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