1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * gmain.c: gtk main routines for pho, an image viewer.
4  *
5  * Copyright 2004 by Akkana Peck.
6  * You are free to use or modify this code under the Gnu Public License.
7  */
8 
9 #include "pho.h"
10 #include "dialogs.h"
11 #include "exif/phoexif.h"
12 
13 #include <gdk/gdk.h>
14 #include <gdk/gdkkeysyms.h>
15 
16 #include <stdlib.h>       /* for getenv() */
17 #include <unistd.h>
18 #include <string.h>
19 #include <ctype.h>
20 
21 char * gCapFileFormat = "Captions";
22 
23 /* Toggle a variable between two modes, preferring the first.
24  * If it's anything but mode1 it will end up as mode1.
25  */
26 #define ToggleBetween(val,mode1,mode2) (val != mode1 ? mode1 : mode2)
27 
28 /* If we're switching into scaling mode because the user pressed - or +/=,
29  * which scaling mode should we switch into?
30  */
ModeForScaling(int oldmode)31 static int ModeForScaling(int oldmode)
32 {
33     switch (oldmode)
34     {
35       case PHO_SCALE_IMG_RATIO:
36       case PHO_SCALE_FULLSIZE:
37       case PHO_SCALE_NORMAL:
38         return PHO_SCALE_IMG_RATIO;
39 
40       case PHO_SCALE_FIXED:
41           return PHO_SCALE_FIXED;
42 
43       case PHO_SCALE_FULLSCREEN:
44       default:
45         return PHO_SCALE_SCREEN_RATIO;
46     }
47 }
48 
RunPhoCommand()49 static void RunPhoCommand()
50 {
51     int i;
52     char* cmd = getenv("PHO_CMD");
53     if (cmd == 0) cmd = "gimp";
54     else if (! *cmd) {
55         if (gDebug)
56             printf("PHO_CMD set to NULL, not running anything\n");
57         return;
58     }
59     else if (gDebug)
60         printf("Calling PHO_CMD %s\n", cmd);
61 
62     gint gargc;
63     gchar** gargv;
64     gchar** new_argv = 0;
65     GError *gerror = 0;
66     if (! g_shell_parse_argv (cmd, &gargc, &gargv, &gerror)) {
67         fprintf(stderr, "Couldn't parse PHO_CMD %s\nError was %s",
68                 cmd, gerror->message);
69         return;
70     }
71 
72     /* PHO_CMD command can have a %s in it; if it does, substitute filename. */
73     int added_arg = 0;
74     for (i=1; i<gargc; ++i) {
75         if (gargv[i][0] == '%' && gargv[i][1] == 's') {
76             gargv[i] = gCurImage->filename;
77             added_arg = 1;
78         }
79     }
80     /* If it didn't have a %s, then we have to allocate a whole new argv array
81      * so we can add the filename argument at the end.
82      * Note that glib expects the argv to be zero terminated --
83      * it provides an argc from g_shell_parse_args() but doesn't
84      * take one in g_spawn_async().
85      */
86     if (! added_arg) {
87         int new_argc = gargc + 2;
88         gchar** new_argv = malloc(sizeof(gchar *) * new_argc);
89         for (i=0; i<gargc; ++i)
90             new_argv[i] = gargv[i];
91         new_argv[gargc] = gCurImage->filename;
92         new_argv[gargc+1] = 0;
93         gargv = new_argv;
94     }
95 
96     if (! g_spawn_async(NULL, gargv,
97                         NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &gerror))
98         fprintf(stderr, "Couldn't spawn %s: \"%s\"\n", cmd, gerror->message);
99 
100     if (new_argv)
101         free(new_argv);
102 }
103 
TryScale(float times)104 void TryScale(float times)
105 {
106     /* Save the view modes, in case this fails */
107     int saveScaleMode = gScaleMode;
108     double saveScaleRatio = gScaleRatio;
109     int saveDisplayMode = gDisplayMode;
110 
111     if (SetViewModes(gDisplayMode, ModeForScaling(gScaleMode),
112                      gScaleRatio * times) == 0)
113         return;
114 
115     /* Oops! It didn't work. Try to reset back to previous settings */
116     SetViewModes(saveDisplayMode, saveScaleMode, saveScaleRatio);
117 }
118 
HandleGlobalKeys(GtkWidget * widget,GdkEventKey * event)119 gint HandleGlobalKeys(GtkWidget* widget, GdkEventKey* event)
120 {
121     if (gDebug) printf("\nKey event\n");
122     if (event->state) {
123         switch (event->keyval)
124         {
125             case GDK_f:
126                 /* Don't respond to ctrl-F -- that might be an attempt
127                  * to edit in a text field in the keywords dialog.
128                  * But we don't do anything on f with any modifier key either.
129                  */
130                 return FALSE;
131             case GDK_0:
132             case GDK_1:
133             case GDK_2:
134             case GDK_3:
135             case GDK_4:
136             case GDK_5:
137             case GDK_6:
138             case GDK_7:
139             case GDK_8:
140             case GDK_9:
141                 if (event->state & GDK_MOD1_MASK) { /* alt-num: add 10 to num */
142                     ToggleNoteFlag(gCurImage, event->keyval - GDK_0 + 10);
143                     return TRUE;
144                 }
145                 return FALSE;
146             case GDK_equal:
147                 if (event->state & GDK_CONTROL_MASK) {
148                     TryScale(1.25);
149                     return TRUE;
150                 }
151                 return FALSE;
152             case GDK_KP_Subtract:
153                 if (event->state & GDK_CONTROL_MASK) {
154                     TryScale(.8);
155                     return TRUE;
156                 }
157                 return FALSE;
158             default:
159                 return FALSE;
160         }
161     }
162 
163     /* Now we know no modifier keys were down. */
164     switch (event->keyval)
165     {
166       case GDK_d:
167           DeleteImage(gCurImage);
168           break;
169       case GDK_space:
170       case GDK_Page_Down:
171       case GDK_KP_Page_Down:
172           /* If we're in slideshow mode, cancel the slideshow */
173           if (gDelayMillis > 0) {
174               gDelayMillis = 0;
175           }
176           else if (NextImage() != 0) {
177               if (Prompt("Quit pho?", "Quit", "Continue", "qx \n", "cn") != 0)
178                   EndSession();
179           }
180           return TRUE;
181       case GDK_BackSpace:
182       case GDK_Page_Up:
183       case GDK_KP_Page_Up:
184           PrevImage();
185           return TRUE;
186       case GDK_Home:
187           gCurImage = 0;
188           NextImage();
189           return TRUE;
190       case GDK_End:
191           gCurImage = gFirstImage->prev;
192           ThisImage();
193           return TRUE;
194       case GDK_n:   /* Get out of any weird display modes */
195           SetViewModes(PHO_DISPLAY_NORMAL, PHO_SCALE_NORMAL, 1.);
196           ShowImage();
197           return TRUE;
198       case GDK_f:   /* Full size mode: show image bit-for-bit */
199           SetViewModes(gDisplayMode,
200                        ToggleBetween(gScaleMode,
201                                      PHO_SCALE_FULLSIZE, PHO_SCALE_NORMAL),
202                        1.);
203           return TRUE;
204       case GDK_F:   /* Full screen mode: as big as possible on screen */
205           SetViewModes(gDisplayMode,
206                        ToggleBetween(gScaleMode,
207                                      PHO_SCALE_FULLSCREEN, PHO_SCALE_NORMAL),
208                        1.);
209           return TRUE;
210       case GDK_p:
211           SetViewModes((gDisplayMode == PHO_DISPLAY_PRESENTATION)
212                        ? PHO_DISPLAY_NORMAL
213                        : PHO_DISPLAY_PRESENTATION,
214                        gScaleMode, gScaleRatio);
215           return TRUE;
216       case GDK_0:
217       case GDK_1:
218       case GDK_2:
219       case GDK_3:
220       case GDK_4:
221       case GDK_5:
222       case GDK_6:
223       case GDK_7:
224       case GDK_8:
225       case GDK_9:
226           ToggleNoteFlag(gCurImage, event->keyval - GDK_0);
227           return TRUE;
228       case GDK_t:   /* make life easier for xv switchers */
229       case GDK_r:
230       case GDK_Right:
231       case GDK_KP_Right:
232           ScaleAndRotate(gCurImage, 90);
233           return TRUE;
234       case GDK_T:   /* make life easier for xv users */
235       case GDK_R:
236       case GDK_l:
237       case GDK_L:
238       case GDK_Left:
239       case GDK_KP_Left:
240           ScaleAndRotate(gCurImage, 270);
241           return TRUE;
242       case GDK_Up:
243       case GDK_Down:
244           ScaleAndRotate(gCurImage, 180);
245           return TRUE;
246       case GDK_plus:
247       case GDK_KP_Add:
248       case GDK_equal:
249           TryScale(2.);
250           return TRUE;
251       case GDK_minus:
252       case GDK_slash:
253       case GDK_KP_Subtract:
254           TryScale(.5);
255           return TRUE;
256       case GDK_g:  /* start gimp, or some other app */
257           RunPhoCommand();
258           return TRUE;
259       case GDK_i:
260           ToggleInfo();
261           return TRUE;
262       case GDK_k:
263           ToggleKeywordsMode();
264           return TRUE;
265       case GDK_o:
266           ChangeWorkingFileSet();
267           return TRUE;
268       case GDK_Escape:
269       case GDK_q:
270           EndSession();
271           return TRUE;
272       default:
273           if (gDebug)
274               printf("Don't know key 0x%lu\n", (unsigned long)(event->keyval));
275           return FALSE;
276     }
277     /* Keep gcc 2.95 happy: */
278     return FALSE;
279 }
280 
AddImage(char * filename)281 PhoImage* AddImage(char* filename)
282 {
283     PhoImage* img = NewPhoImage(filename);
284     if (gDebug)
285         printf("Adding image %s\n", filename);
286     if (!img) {
287         fprintf(stderr, "Out of memory!\n");
288         exit(1);
289     }
290     /* Make img the new last image in the list */
291     AppendItem(img);
292     return img;
293 }
294 
parseGeom(char * geom,int * width,int * height)295 static void parseGeom(char* geom, int* width, int* height)
296 {
297     int num;
298     if (!isdigit(*geom)) {
299         printf("Geometry '%s' must start with a digit\n", geom);
300         Usage();
301     }
302     num = atoi(geom);
303     if (num > 0)
304         *width = num;
305 
306     /* Now see if there's a height */
307     while (*(++geom)) {
308         if (*geom == 'x') {
309             ++geom;
310             if (!isdigit(*geom)) {
311                 printf("Number after 'x' in geometry must start with a digit, not %s\n", geom);
312                 Usage();
313             }
314             num = atoi(geom);
315             if (num > 0)
316                 *height = num;
317             return;
318         }
319     }
320 }
321 
322 /* CheckArg takes a string, like -Pvg, and sets all the relevant flags. */
CheckArg(char * arg)323 static void CheckArg(char* arg)
324 {
325     for ( ; *arg != 0; ++arg)
326     {
327         if (*arg == '-')
328             ;
329         else if (*arg == 'd')
330             gDebug = 1;
331         else if (*arg == 'h')
332             Usage();
333         else if (*arg == 'v')
334             VerboseHelp();
335         else if (*arg == 'n')
336             gMakeNewWindows = 1;
337         else if (*arg == 'p') {
338             gDisplayMode = PHO_DISPLAY_PRESENTATION;
339             if (arg[1] == '\0') {
340                 /* Normal presentation mode -- center everything
341                  * and size to the current display.
342                  */
343                 gPresentationWidth = 0;
344                 gPresentationHeight = 0;
345             } else {
346                 char* geom = arg+1;
347                 if (*geom == 'p') ++geom;
348                 gPresentationWidth = 0;
349                 gPresentationHeight = 0;
350                 parseGeom(geom, &gPresentationWidth, &gPresentationHeight);
351 
352                 /* Check for width without height, in case the user
353                  * ran something like pho -p1024, which is almost
354                  * certainly a mistake.
355                  */
356                 if (gPresentationWidth && !gPresentationHeight) {
357                     fprintf(stderr,
358                             "Presentation width %d given with no height.\n\n",
359                             gPresentationWidth);
360                     Usage();
361                 }
362                 /* Note there's no check for the opposite, height without width.
363                  * For that, the user would have had to specify something like
364                  * 0x768; in the unlikely event the user does that,
365                  * maybe it means they really want it for some strange reason.
366                  */
367             }
368         } else if (*arg == 'P')
369             gDisplayMode = PHO_DISPLAY_NORMAL;
370         else if (*arg == 'k') {
371             gDisplayMode = PHO_DISPLAY_KEYWORDS;
372             gScaleMode = PHO_SCALE_FIXED;
373             gScaleRatio = 0.0;
374         } else if (*arg == 's') {
375             /* find the slideshow delay time, from e.g. pho -s2 */
376             if (isdigit(arg[1]) || arg[1] == '.')
377                 gDelayMillis = (int)(atof(arg+1) * 1000.);
378             else Usage();
379             if (gDebug)
380                 printf("Slideshow delay %d milliseconds\n", gDelayMillis);
381         } else if (*arg == 'r') {
382             gRepeat = 1;
383         } else if (*arg == 'c') {
384             gCapFileFormat = strdup(arg+1);
385             if (gDebug)
386                 printf("Format set to '%s'\n", gCapFileFormat);
387 
388             /* Can't follow this with any other letters -- they're all
389              * part of the filename -- so return.
390              */
391             return;
392         }
393     }
394 }
395 
main(int argc,char ** argv)396 int main(int argc, char** argv)
397 {
398     /* Initialize some defaults from environment variables,
399      * before reading cmdline args.
400      */
401     int options = 1;
402 
403     char* env = getenv("PHO_ARGS");
404     if (env && *env)
405         CheckArg(env);
406 
407     while (argc > 1)
408     {
409         if (argv[1][0] == '-' && options) {
410             if (strcmp(argv[1], "--"))
411                 CheckArg(argv[1]);
412             else
413                 options = 0;
414         }
415         else {
416             AddImage(argv[1]);
417         }
418         --argc;
419         ++argv;
420     }
421 
422     if (gFirstImage == 0)
423         Usage();
424 
425     /* Initialize some variables associated with the notes flags */
426     InitNotes();
427 
428     /* See http://www.gtk.org/tutorial */
429     gtk_init(&argc, &argv);
430 
431     /* Must init rgb system explicitly, else we'll crash
432      * in the first gdk_pixbuf_render_to_drawable(),
433      * calling gdk_draw_rgb_image_dithalign():
434      * (Is this still true in gtk2?)
435      */
436     gdk_rgb_init();
437 
438     gPhysMonitorWidth = gMonitorWidth = gdk_screen_width();
439     gPhysMonitorHeight = gMonitorHeight = gdk_screen_height();
440 
441     /* Load the first image */
442     if (NextImage() != 0)
443         exit(1);
444 
445     gtk_main();
446     return 0;
447 }
448 
449