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