1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * pho.c: core 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 <stdlib.h>
14 #include <stdio.h>
15 #include <errno.h>
16 #include <string.h>
17 #include <unistd.h>    /* for unlink() */
18 #include <fcntl.h>     /* for symbols like O_RDONLY */
19 
20 /* ************* Definition of globals ************ */
21 PhoImage* gFirstImage = 0;
22 PhoImage* gCurImage = 0;
23 
24 /* Monitor resolution */
25 int gMonitorWidth = 0;
26 int gMonitorHeight = 0;
27 
28 /* Effective resolution, e.g. if we'll be sending to a projector */
29 int gPresentationWidth = 0;
30 int gPresentationHeight = 0;
31 
32 /* We only have one image at a time, so make it global. */
33 GdkPixbuf* gImage = 0;
34 
35 int gDebug = 0;    /* debugging messages */
36 
37 int gScaleMode = PHO_SCALE_NORMAL;
38 double gScaleRatio = 1.0;
39 
40 int gDisplayMode = PHO_DISPLAY_NORMAL;
41 
42 /* Slideshow delay is zero by default -- no slideshow. */
43 int gDelayMillis = 0;
44 int gPendingTimeout = 0;
45 
46 /* Loop back to the first image after showing the last one */
47 int gRepeat = 0;
48 
49 static int RotateImage(PhoImage* img, int degrees);    /* forward */
50 
DelayTimer(gpointer data)51 static gint DelayTimer(gpointer data)
52 {
53     if (gDelayMillis == 0)    /* slideshow mode was cancelled */
54         return FALSE;
55 
56     if (gDebug) printf("-- Timer fired\n");
57     gPendingTimeout = 0;
58 
59     NextImage();
60     return FALSE;       /* cancel the timer */
61 }
62 
ShowImage()63 int ShowImage()
64 {
65     ScaleAndRotate(gCurImage, 0);
66     /* Keywords dialog will be updated if necessary from DrawImage */
67 
68     if (gDelayMillis > 0 && gPendingTimeout == 0
69         && (gCurImage->next != 0 || gCurImage->next != gFirstImage)) {
70         if (gDebug) printf("Adding timeout for %d msec\n", gDelayMillis);
71         gPendingTimeout = g_timeout_add (gDelayMillis, DelayTimer, 0);
72     }
73 
74     return 0;
75 }
76 
LoadImageFromFile(PhoImage * img)77 static int LoadImageFromFile(PhoImage* img)
78 {
79     GError* err = NULL;
80     int rot;
81 
82     if (img == 0)
83         return -1;
84 
85     if (gDebug)
86         printf("LoadImageFromFile(%s)\n", img->filename);
87 
88     /* Free the current image */
89     if (gImage) {
90         g_object_unref(gImage);
91         gImage = 0;
92     }
93 
94     gImage = gdk_pixbuf_new_from_file(img->filename, &err);
95     if (!gImage)
96     {
97         gImage = 0;
98         fprintf(stderr, "Can't open %s: %s\n", img->filename, err->message);
99         return -1;
100     }
101     ReadCaption(img);
102 
103     img->curWidth = gdk_pixbuf_get_width(gImage);
104     img->curHeight = gdk_pixbuf_get_height(gImage);
105 
106     /* The first time an image is loaded, it should be rotated
107      * to its appropriate EXIF rotation. Subsequently, though,
108      * it should be rotated to curRot.
109      */
110     if (img->trueWidth == 0 || img->trueHeight == 0) {
111         /* Read the EXIF rotation if we haven't already rotated this image */
112         ExifReadInfo(img->filename);
113         if (HasExif() && (rot = ExifGetInt(ExifOrientation)) != 0)
114             img->exifRot = rot;
115         else
116             img->exifRot = 0;
117     }
118 
119     /* trueWidth and Height used to be set inside EXIF clause,
120      * but that doesn't make sense -- we need it not just the first
121      * time, but also ever time the image is reloaded.
122      */
123     img->trueWidth = img->curWidth;
124     img->trueHeight = img->curHeight;
125 
126     return 0;
127 }
128 
LoadImageAndRotate(PhoImage * img)129 static int LoadImageAndRotate(PhoImage* img)
130 {
131     int e;
132     int rot = (img ? img->curRot : 0);
133     int firsttime = (img && (img->trueWidth == 0));
134 
135     if (!img) return -1;
136 
137     img->trueWidth = img->trueHeight = img->curRot = 0;
138 
139     e = LoadImageFromFile(img);
140     if (e) return e;
141 
142     /* If it's not the first time we've loaded this image,
143      * default its rotation to the EXIF rotation if any.
144      * Otherwise rotate to the saved img->curRot.
145      */
146     if (firsttime && img->exifRot != 0)
147         ScaleAndRotate(gCurImage, img->exifRot);
148 
149     else
150         ScaleAndRotate(gCurImage, rot);
151 
152     return 0;
153 }
154 
155 /* ThisImage() is called when gCurImage has changed and needs to
156  * be reloaded.
157  */
ThisImage()158 int ThisImage()
159 {
160     if (LoadImageAndRotate(gCurImage) != 0)
161         return NextImage();
162     ShowImage();
163     return 0;
164 }
165 
166 /* Go to the next image after gCurImage,
167  * or the first image if gCurImage isn't set. If an image fails to load,
168  * delete it from the image list and move on to the next image.
169  */
NextImage()170 int NextImage()
171 {
172     if (gDebug)
173         printf("\n================= NextImage ====================\n");
174 
175     PhoImage* origCurImage = gCurImage;
176 
177     /* Loop, since images may fail to load
178      * and may need to be deleted from the list
179      */
180     while (1)
181     {
182         if (! gFirstImage) {
183             /* There's no list! How can we go to the next item? */
184             if (gDebug)
185                 printf("NextImage: empty list!\n");
186             return -1;
187         }
188 
189         if (! gCurImage) {
190             if (gDebug)
191                 printf("NextImage: going to first image\n");
192             gCurImage = gFirstImage;
193         }
194         else if (gCurImage->next == gFirstImage) {  /* end of the list */
195             return -1;
196         }
197         else {
198             gCurImage = gCurImage->next;
199         }
200 
201         if (LoadImageAndRotate(gCurImage) == 0) {   /* Success! */
202             ShowImage();
203             return 0;
204         }
205 
206         /* gCurImage failed to load. */
207         if (gDebug)
208             printf("Skipping '%s' (didn't load)\n", gCurImage->filename);
209         DeleteItem(gCurImage);
210         gCurImage = origCurImage;
211     }
212 }
213 
PrevImage()214 int PrevImage()
215 {
216     if (gDebug)
217         printf("\n================= PrevImage ====================\n");
218     do {
219         if (gCurImage == 0) {  /* no image loaded yet, first call */
220             gCurImage = gFirstImage;
221             if (gCurImage->prev)
222                 gCurImage = gCurImage->prev;
223         }
224         else {
225             if (gCurImage == gFirstImage)
226                 return -1;  /* end of list */
227             gCurImage = gCurImage->prev;
228         }
229     } while (LoadImageAndRotate(gCurImage) != 0);
230     ShowImage();
231     return 0;
232 }
233 
234 /* Limit new_width and new_height so that they're no bigger than
235  * max_width and max_height. This doesn't actually scale, just
236  * calculates dimensions and returns them in *width and *height.
237  */
ScaleToFit(int * width,int * height,int max_width,int max_height,int scaleMode,double scaleRatio)238 static void ScaleToFit(int *width, int *height,
239                        int max_width, int max_height,
240                        int scaleMode, double scaleRatio)
241 {
242     int new_w, new_h;
243 
244     if (scaleMode == PHO_SCALE_SCREEN_RATIO) {
245         /* Which dimension needs to be scaled down more? */
246         double xratio = (double)(*width) / max_width;
247         double yratio = (double)(*height) / max_height;
248 
249         if (xratio > 1. || yratio > 1.) {    /* need some scaling */
250             if (xratio > yratio) {  /* X needs more scaling down */
251                 new_w = *width / xratio;
252                 new_h = *height / xratio;
253             } else {                /* Y needs more scaling down */
254                 new_w = *width / yratio;
255                 new_h = *height / yratio;
256             }
257         }
258     }
259     else {
260         double xratio, yratio;
261         if (scaleMode == PHO_SCALE_FIXED) {
262             /* Special case: scaleRatio isn't actually a ratio,
263              * it's the max size we want the image's long dimension to be.
264              */
265             if (*width > *height) {
266                 new_w = scaleRatio;
267                 new_h = scaleRatio * *height / *width;
268             }
269             else {
270                 new_w = scaleRatio * *width / *height;
271                 new_h = scaleRatio;
272             }
273             /* Reset scaleRatio so it can now be used for (no) scaling */
274             scaleRatio = 1.0;
275         }
276         else {
277             new_w = *width;
278             new_h = *height;
279         }
280         new_w *= scaleRatio;
281         new_h *= scaleRatio;
282         xratio = (double)new_w / (double)max_width;
283         yratio = (double)new_h / (double)max_height;
284         if (xratio > 1. || yratio > 1.) {
285             if (xratio > yratio) {
286                 new_w /= xratio;
287                 new_h /= xratio;
288             } else {
289                 new_w /= yratio;
290                 new_h /= yratio;
291             }
292         }
293     }
294 
295     *width = new_w * scaleRatio;
296     *height = new_h * scaleRatio;
297 }
298 
299 #define SWAP(a, b) { int temp = a; a = b; b = temp; }
300 /*#define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;}*/
301 
302 /* Rotate the image according to the current scale mode, scaling as needed,
303  * then redisplay.
304  *
305  * This will read the image from disk if necessary,
306  * and it will rotate the image at the appropriate time
307  * (when the image is at its smallest).
308  *
309  * This is the routine that should be called by external callers:
310  * callers should never need to call RotateImage.
311  *
312  * degrees is the increment from the current rotation (curRot).
313  */
ScaleAndRotate(PhoImage * img,int degrees)314 int ScaleAndRotate(PhoImage* img, int degrees)
315 {
316 #define true_width img->trueWidth
317 #define true_height img->trueHeight
318     int new_width;
319     int new_height;
320 
321     if (gDebug)
322         printf("ScaleAndRotate(%d (cur = %d))\n", degrees, img->curRot);
323 
324     /* degrees should be between 0 and 360 */
325     degrees = (degrees + 360) % 360;
326 
327     /* First, load the image if we haven't already, to get true w/h */
328     if (true_width == 0 || true_height == 0) {
329         if (gDebug) printf("Loading, first time, from ScaleAndRotate!\n");
330         LoadImageFromFile(img);
331     }
332 
333     /*
334      * Calculate new_width and new_height, the size to which the image
335      * should be scaled before or after rotation,
336      * based on the current scale mode.
337      * That means that if the aspect ratio is changing,
338      * new_width will be the image's height after rotation.
339      */
340 
341     /* Fullsize: display always at real resolution,
342      * even if it's too big to fit on the screen.
343      */
344     if (gScaleMode == PHO_SCALE_FULLSIZE) {
345         new_width = true_width * gScaleRatio;
346         new_height = true_height * gScaleRatio;
347         if (gDebug) printf("Now fullsize, %dx%d\n", new_width, new_height);
348     }
349 
350     /* Normal: display at full size unless it won't fit the screen,
351      * in which case scale it down.
352      */
353 #define NORMAL_SCALE_SLOP 5
354     else if (gScaleMode == PHO_SCALE_NORMAL
355              || gScaleMode == PHO_SCALE_SCREEN_RATIO
356              || gScaleMode == PHO_SCALE_FIXED)
357     {
358         int max_width, max_height;
359         int aspect_changing;    /* Is the aspect ratio changing? */
360 
361         new_width = true_width;
362         new_height = true_height;
363 
364         aspect_changing = ((degrees % 180) != 0);
365         if (aspect_changing) {
366             max_width = gMonitorHeight;
367             max_height = gMonitorWidth;
368             if (gDebug)
369                 printf("Aspect ratio is changing\n");
370         } else {
371             max_width = gMonitorWidth;
372             max_height = gMonitorHeight;
373         }
374 
375         /* If we're in fixed mode, make sure we've set the "scale ratio"
376          * to the screen size:
377          */
378         if (gScaleMode == PHO_SCALE_FIXED && gScaleRatio == 0.0)
379             gScaleRatio = FracOfScreenSize();
380 
381         if (new_width > max_width || new_height > max_height) {
382             ScaleToFit(&new_width, &new_height, max_width, max_height,
383                        gScaleMode, gScaleRatio);
384         }
385 
386 #if 0
387         /* Special case: if in PHO_SCALE_SCREEN_RATIO, we don't need to
388          * scale verticals (assuming a landscape screen) down -- it's
389          * better to keep the max dimension of each image the same.
390          */
391         if (new_width <= max_height && new_height <= max_width) {
392             double r =
393         } else {
394             new_width *= gScaleRatio;
395             new_height *= gScaleRatio;
396         }
397 #endif
398 
399         /* See if new_width and new_height are close enough already
400          * that it might not be worth doing the work of scaling:
401          */
402         if (abs(img->curWidth - new_width) + abs(img->curHeight - new_height)
403             < NORMAL_SCALE_SLOP) {
404             new_width = img->curWidth;
405             new_height = img->curHeight;
406         }
407     }
408 
409     else if (gScaleMode == PHO_SCALE_IMG_RATIO) {
410         new_width = true_width * gScaleRatio;
411         new_height = true_height * gScaleRatio;
412 
413         /* See if we're close */
414         if (abs(img->curWidth - new_width) + abs(img->curHeight - new_height)
415             < NORMAL_SCALE_SLOP) {
416             new_width = img->curWidth;
417             new_height = img->curHeight;
418         }
419     }
420 
421     /* Fullscreen: Scale either up or down if necessary to make
422      * the largest dimension match the screen size.
423      */
424 #define FULLSCREEN_SCALE_SLOP 20
425     else if (gScaleMode == PHO_SCALE_FULLSCREEN) {
426         /* If we're in presentation mode, then we need to scale
427          * to the current size of the window, not the monitor,
428          * because in xinerama gdk_window_fullscreen() will fullscreen
429          * onto only one monitor, but gdk_screen_width() gives the
430          * width of the full xinerama setup.
431          */
432         int diffx = abs(img->curWidth - gMonitorWidth);
433         int diffy = abs(img->curHeight - gMonitorHeight);
434         double xratio, yratio;
435         gint screenwidth, screenheight;
436 
437         if (gDisplayMode == PHO_DISPLAY_PRESENTATION)
438             gtk_window_get_size(GTK_WINDOW(gWin), &screenwidth, &screenheight);
439         else {
440             screenwidth = gMonitorWidth;
441             screenheight = gMonitorHeight;
442         }
443 
444         if (diffx < FULLSCREEN_SCALE_SLOP || diffy < FULLSCREEN_SCALE_SLOP) {
445             new_width = img->curWidth;
446             new_height = img->curHeight;
447         }
448         else {
449             xratio = (double)screenwidth / true_width;
450             yratio = (double)screenheight / true_height;
451 
452             /* Use xratio for the more extreme of the two */
453             if (xratio > yratio) xratio = yratio;
454             new_width = xratio * true_width;
455             new_height = xratio * true_height;
456         }
457     }
458     else {
459         /* Shouldn't ever happen, means gScaleMode is bogus */
460         printf("Internal error: Unknown scale mode %d\n", gScaleMode);
461         new_width = img->curWidth;
462         new_height = img->curHeight;
463     }
464     /*
465      * Finally, we're done with the scaling modes.
466      * Time to do the scaling and rotation,
467      * reloading the image if needed.
468      */
469 
470     /* First figure out if we're getting bigger and hence need to reload. */
471     if ((new_width > img->curWidth || new_height > img->curHeight)
472         && (img->curWidth < true_width && img->curHeight < true_height)) {
473         if (gDebug)
474             printf("Getting bigger, from %dx%d to %dx%d -- need to reload\n",
475                    img->curWidth, img->curHeight, new_width, new_height);
476 
477         /* Because curRot is going back to zero, that means we
478          * might need to swap new_width and new_height, in case
479          * the aspect ratio is changing.
480         if (degrees % 180 != 0) {
481         if ((img->curRot + degrees) % 180 != 0) {
482          * new_width and new_height have already taken into account
483          * the desired rotation (degrees); the important thing is
484          * whether the current rotation is 90 degrees off.
485          */
486         if (img->curRot % 180 != 0) {
487             SWAP(new_width, new_height);
488             if (gDebug)
489                 printf("Swapping new width/height: %dx%d\n",
490                        new_width, new_height);
491         }
492 
493         /* image->curRot is going to be set back to zero when we load
494          * from file, so adjust current planned rotation accordingly.
495          */
496         degrees = (degrees + img->curRot + 360) % 360;
497             /* Now it's the absolute end rot desired */
498 
499         img->curRot = 0;
500         LoadImageFromFile(img);
501     }
502 #if 0
503     else if (degrees % 180 != 0) {
504         SWAP(new_width, new_height);
505         printf("Not reloading, but swapped new width/height, now %dx%d\n",
506                new_width, new_height);
507     }
508 
509     /* new_* are the sizes we want after rotation. But we need
510      * to compare them now to the sizes before rotation. So we
511      * need to compensate for rotation.
512      */
513 
514     /* If we're going to scale up, then do the rotation first,
515      * before scaling. Otherwise, scale down first then rotate.
516      */
517     if (degrees != 0 &&
518         (new_width > img->curWidth || new_height > img->curHeight)) {
519         if (gDebug) printf("Rotating before scaling up\n");
520         RotateImage(img, degrees);
521         degrees = 0;    /* finished with rotation */
522     }
523 #endif
524 
525     /* Do the scaling (thought we'd never get there!) */
526     if (new_width != img->curWidth || new_height != img->curHeight)
527     {
528         GdkPixbuf* newimage = gdk_pixbuf_scale_simple(gImage,
529                                                       new_width,
530                                                       new_height,
531                                                       GDK_INTERP_BILINEAR);
532         /* If that's too slow use GDK_INTERP_NEAREST */
533 
534         /* scale_simple apparently has no error return; if it fails,
535          * it still returns a pixbuf but width and height are -1.
536          * Hope this undocumented behavior doesn't change!
537          * Later: now it's documented. Whew.
538          */
539         if (!newimage || gdk_pixbuf_get_width(newimage) < 1) {
540             if (newimage)
541                 g_object_unref(newimage);
542             printf("\007Error scaling from %d x %d to %d x %d: probably out of memory\n",
543                    img->curWidth, img->curHeight, new_width, new_height);
544             Prompt("Couldn't scale up: probably out of memory", "Bummer", 0,
545                    "\n ", "");
546             return -1;
547         }
548         if (gDebug)
549             printf("Scale from %dx%d = %dx%d to %dx%d = %dx%d\n",
550                    img->curWidth, img->curHeight,
551                    gdk_pixbuf_get_width(gImage), gdk_pixbuf_get_height(gImage),
552                    new_width, new_height,
553                    gdk_pixbuf_get_width(newimage),
554                    gdk_pixbuf_get_height(newimage));
555         if (gImage)
556             g_object_unref(gImage);
557         gImage = newimage;
558 
559         img->curWidth = gdk_pixbuf_get_width(gImage);
560         img->curHeight = gdk_pixbuf_get_height(gImage);
561     }
562 
563     /* If we didn't rotate before, do it now. */
564     if (degrees != 0)
565         RotateImage(img, degrees);
566 
567     /* We've finished making our changes. Now we may need to make
568      * changes in the window size or position.
569      */
570     PrepareWindow();
571     return 0;
572 }
573 
NewPhoImage(char * fnam)574 PhoImage* NewPhoImage(char* fnam)
575 {
576     PhoImage* newimg = calloc(1, sizeof (PhoImage));
577     if (newimg == 0) return 0;
578     newimg->filename = fnam;  /* no copy, we don't own the memory */
579 
580     return newimg;
581 }
582 
ReallyDelete(PhoImage * delImg)583 void ReallyDelete(PhoImage* delImg)
584 {
585     /* Make sure the keywords dialog doesn't save a pointer to this image */
586     NoCurrentKeywords();
587 
588     if (unlink(delImg->filename) < 0)
589     {
590         printf("OOPS!  Can't delete %s\n", delImg->filename);
591         return;
592     }
593 
594     DeleteItem(delImg);
595 
596     /* If we just deleted the only image, all we can do is quit */
597     if (!gFirstImage)
598         EndSession();
599 
600     ThisImage();
601 }
602 
DeleteImage(PhoImage * delImg)603 void DeleteImage(PhoImage* delImg)
604 {
605     char buf[512];
606     if (delImg->filename == 0)
607         return;
608     sprintf(buf, "Delete file %s?", delImg->filename);
609     if (Prompt(buf, "Delete", 0, "dD\n", "nN") > 0)
610         ReallyDelete(delImg);
611 }
612 
613 /* RotateImage just rotates an existing image, no scaling or reloading.
614  * It's typically called from ScaleAndRotate either just
615  * before or just after scaling.
616  * No one except ScaleAndRotate should call it.
617  * Degrees is the amount of rotation relative to current.
618  */
RotateImage(PhoImage * img,int degrees)619 static int RotateImage(PhoImage* img, int degrees)
620 {
621     guchar *oldpixels, *newpixels;
622     int x, y;
623     int oldrowstride, newrowstride, nchannels, bitsper, alpha;
624     int newWidth, newHeight, newTrueWidth, newTrueHeight;
625     GdkPixbuf* newImage;
626 
627     if (!gImage) return 1;     /* sanity check */
628 
629     if (gDebug)
630         printf("RotateImage(%d), initially %d x %d, true %dx%d\n",
631                degrees, img->curWidth, img->curHeight,
632                img->trueWidth, img->trueHeight);
633 
634     /* Make sure degrees is between 0 and 360 even if it's -90 */
635     degrees = (degrees + 360) % 360;
636 
637     /* degrees might be zero now, since we might be rotating back to zero. */
638     if (degrees == 0) {
639         return 0;
640     }
641 
642     /* Swap X and Y if appropriate */
643     if (degrees == 90 || degrees == 270)
644     {
645         newWidth = img->curHeight;
646         newHeight = img->curWidth;
647         newTrueWidth = img->trueHeight;
648         newTrueHeight = img->trueWidth;
649     }
650     else
651     {
652         newWidth = img->curWidth;
653         newHeight = img->curHeight;
654         newTrueWidth = img->trueWidth;
655         newTrueHeight = img->trueHeight;
656     }
657 
658     oldrowstride = gdk_pixbuf_get_rowstride(gImage);
659     /* Sometimes rowstride is slightly different from width*nchannels:
660      * gdk_pixbuf optimizes by aligning to 32-bit boundaries.
661      * But apparently it works even if rowstride is not aligned,
662      * just might not be as fast.
663      * XXX check newrowstride alignment
664      */
665     bitsper = gdk_pixbuf_get_bits_per_sample(gImage);
666     nchannels = gdk_pixbuf_get_n_channels(gImage);
667     alpha = gdk_pixbuf_get_has_alpha(gImage);
668 
669     oldpixels = gdk_pixbuf_get_pixels(gImage);
670 
671     newImage = gdk_pixbuf_new(GDK_COLORSPACE_RGB, alpha, bitsper,
672                               newWidth, newHeight);
673     if (!newImage) return 1;
674     newpixels = gdk_pixbuf_get_pixels(newImage);
675     newrowstride = gdk_pixbuf_get_rowstride(newImage);
676 
677     for (x = 0; x < img->curWidth; ++x)
678     {
679         for (y = 0; y < img->curHeight; ++y)
680         {
681             int newx, newy;
682             int i;
683             switch (degrees)
684             {
685               case 90:
686                 newx = img->curHeight - y - 1;
687                 newy = x;
688                 break;
689               case 270:
690                 newx = y;
691                 newy = img->curWidth - x - 1;
692                 break;
693               case 180:
694                 newx = img->curWidth - x - 1;
695                 newy = img->curHeight - y - 1;
696                 break;
697               default:
698                 printf("Illegal rotation value!\n");
699                 return 1;
700             }
701             for (i=0; i<nchannels; ++i)
702                 newpixels[newy*newrowstride + newx*nchannels + i]
703                     = oldpixels[y*oldrowstride + x*nchannels + i];
704         }
705     }
706 
707     img->curWidth = newWidth;
708     img->curHeight = newHeight;
709     img->trueWidth = newTrueWidth;
710     img->trueHeight = newTrueHeight;
711 
712     img->curRot = (img->curRot + degrees + 360) % 360;
713 
714     g_object_unref(gImage);
715     gImage = newImage;
716 
717     return 0;
718 }
719 
Usage()720 void Usage()
721 {
722     printf("pho version %s.  Copyright 2002-2009 Akkana Peck akkana@shallowsky.com.\n", VERSION);
723     printf("Usage: pho [-dhnp] image [image ...]\n");
724     printf("\t-p:  Presentation mode (full screen, centered)\n");
725     printf("\t-p[resolution]: Projector mode:\n\tlike presentation mode but in upper left corner\n");
726     printf("\t-P:  No presentation mode (separate window) -- default\n");
727     printf("\t-k:  Keywords mode (show a Keywords dialog for each image)\n");
728     printf("\t-n:  Replace each image window with a new window (helpful for some window managers)\n");
729     printf("\t-sN: Slideshow mode, where N is the timeout in seconds\n");
730     printf("\t-r:  Repeat: loop back to the first image after showing the last\n");
731     printf("\t-cpattern: Caption/Comment file pattern, format string for reworking filename\n");
732     printf("\t--:  Assume no more flags will follow\n");
733     printf("\t-d:  Debug messages\n");
734     printf("\t-h:  Help: Print this summary\n");
735     printf("\t-v:  Verbose help: Print a summary of key bindings\n");
736     exit(1);
737 }
738 
VerboseHelp()739 void VerboseHelp()
740 {
741     printf("pho version %s.  Copyright 2002,2003,2004,2007 Akkana Peck akkana@shallowsky.com.\n", VERSION);
742     printf("Type pho -h for commandline arguments.\n");
743     printf("\npho Key Bindings:\n\n");
744     printf("<space>, <Page Down>\n\tNext image (or cancel slideshow mode)\n");
745     printf("<backspace>, <Page Up>\n\tPrevious image\n");
746     printf("<home>\tFirst image\n");
747     printf("f\tToggle full-size mode (even if bigger than screen)\n");
748     printf("F\tToggle fullscreen mode (scale even small images up to fullscreen)\n");
749     printf("k\tTurn on keywords mode: show the keywords dialog\n");
750     printf("p\tToggle presentation mode (take up the whole screen, centering the image)\n");
751     printf("d\tDelete current image (from disk, after confirming with another d)\n");
752     printf("0-9\tRemember image in note list 0 through 9 (to be printed at exit)\n");
753     printf("\t(In keywords dialog, alt + 0-9 adds 10, e.g. alt-4 triggers flag 14.\n");
754     printf("t, r, <Right>\n\tRotate right 90 degrees\n");
755     printf("T, R, l, L, <Left>]n\tRotate left 90 degrees\n");
756     printf("<up>\tRotate 180 degrees\n");
757     printf("+, =\tDouble size\n");
758     printf("/, -\tHalf size\n");
759     printf("i\tShow/hide info dialog\n");
760     printf("o\tChange the working file set (add files or make a new list)\n");
761     printf("g\tRun gimp on the current image\n");
762     printf("\t(or set PHO_CMD to an alternate command)\n");
763     printf("q\tQuit\n");
764     printf("<esc>\tQuit (or hide a dialog, if one is showing)\n");
765     printf("\n");
766     printf("Pho mouse bindings:\n");
767     printf("In presentation mode: drag with middlemouse to pan/move.\n");
768     printf("\n");
769     printf("Pho environment variables:\n");
770     printf("PHO_ARGS: default arguments (e.g. -p)\n");
771     printf("PHO_CMD : command to run when pressing g (default: gimp).\n");
772     printf("          Use an empty string if you don't want any command.\n");
773     exit(0);
774 }
775 
776