1 /*
2  * gdialogs.c: gtk dialogs used in pho, an image viewer.
3  *
4  * Copyright 2004 by Akkana Peck.
5  * You are free to use or modify this code under the Gnu Public License.
6  */
7 
8 #include "pho.h"
9 #include "dialogs.h"
10 #include "exif/phoexif.h"
11 
12 #include <gdk/gdkkeysyms.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <stdlib.h>    /* for free() */
16 #include <unistd.h>
17 
18 static GtkWidget* InfoDialog = 0;
19 static GtkWidget* InfoDEntry = 0;
20 static GtkWidget* InfoDImgName = 0;
21 static GtkWidget* InfoDImgSize = 0;
22 static GtkWidget* InfoDOrigSize = 0;
23 static GtkWidget* InfoDImgRotation = 0;
24 static GtkWidget* InfoFlag[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
25 static GtkWidget* InfoExifContainer;
26 static GtkWidget* InfoExifEntries[NUM_EXIF_FIELDS];
27 static PhoImage* sCurInfoImage = 0;
28 
29 /* It turns out that trying to have two dialogs both transient
30  * to the same main window causes bad things to happen.
31  * So guard against that.
32  */
KeepOnTop(GtkWidget * dialog)33 void KeepOnTop(GtkWidget* dialog)
34 {
35     static GtkWidget* sCurTransientDlg = 0;
36     static GtkWidget* sCurTransientOwner = 0;
37 
38     if (sCurTransientDlg != dialog && sCurTransientOwner != gWin)
39     {
40         gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(gWin));
41         sCurTransientDlg = dialog;
42         sCurTransientOwner = gWin;
43     }
44 }
45 
AddComment(PhoImage * img,char * txt)46 static void AddComment(PhoImage* img, char* txt)
47 {
48     if (img->comment != 0)
49         free(img->comment);
50     img->comment = strdup(txt);
51 }
52 
53 /* Update the image according to whatever has changed in the dialog.
54  * Call this before popping down or quitting,
55  * and before going to next or prev image.
56  */
UpdateImage()57 static void UpdateImage()
58 {
59     int i;
60     unsigned mask, flags;
61     char* text;
62 
63     if (!InfoDialog || !InfoDialog->window || !IsVisible(InfoDialog)
64         || !sCurInfoImage)
65         return;
66 
67     text = (char*)gtk_entry_get_text((GtkEntry*)InfoDEntry);
68     if (text && *text)
69         AddComment(sCurInfoImage, text);
70 
71     flags = 0;
72     for (i=0, mask=1; i<10; ++i, mask <<= 1)
73     {
74         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(InfoFlag[i])))
75             flags |= mask;
76     }
77     sCurInfoImage->noteFlags = flags;
78 }
79 
PopdownInfoDialog()80 static void PopdownInfoDialog()
81 {
82     UpdateImage();
83     if (IsVisible(InfoDialog))
84         gtk_widget_hide(InfoDialog);
85 }
86 
SetInfoDialogToggle(int which,int newval)87 void SetInfoDialogToggle(int which, int newval)
88 {
89     if (InfoFlag[which])
90         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(InfoFlag[which]),
91                                      newval ? TRUE : FALSE);
92 }
93 
UpdateInfoDialog()94 void UpdateInfoDialog()
95 {
96     char buffer[256];
97     char* s;
98     int i, mask, flags;
99 
100     if (!gCurImage || !InfoDialog || !InfoDialog->window)
101         /* Don't need to check whether it's visible -- if we're not
102          * about to show the dialog we shouldn't be calling this anyway.
103          */
104         return;
105     if (gDebug)
106         printf("UpdateInfoDialog() for %s\n", gCurImage->filename);
107 
108     /* Update the last image according to the current dialog */
109     if (gCurImage == sCurInfoImage) {
110         if (gDebug) printf("Already up-to-date\n");
111         return;
112     }
113     if (sCurInfoImage) {
114         if (gDebug) printf("Calling UpdateImage\n");
115         UpdateImage();
116     }
117 
118     if (gCurImage == 0)
119         return;
120 
121     /* Now update the dialog for the new image */
122     sCurInfoImage = gCurImage;
123 
124     /* In case the image's window has changed, make sure we're
125      * staying on top of the right window:
126      */
127     KeepOnTop(InfoDialog);
128 
129     sprintf(buffer, "pho: %s info", gCurImage->filename);
130     gtk_window_set_title(GTK_WINDOW(InfoDialog), buffer);
131 
132     s = gCurImage->comment;
133     gtk_entry_set_text(GTK_ENTRY(InfoDEntry), s ? s : "");
134 
135     gtk_label_set_text(GTK_LABEL(InfoDImgName), gCurImage->filename);
136     sprintf(buffer, "%d x %d", gCurImage->trueWidth, gCurImage->trueHeight);
137     gtk_label_set_text(GTK_LABEL(InfoDOrigSize), buffer);
138     sprintf(buffer, "%d x %d", gCurImage->curWidth, gCurImage->curHeight);
139     gtk_label_set_text(GTK_LABEL(InfoDImgSize), buffer);
140     switch (gCurImage->curRot)
141     {
142       case 90:
143           gtk_label_set_text(GTK_LABEL(InfoDImgRotation), " 90 ");
144           break;
145       case 180:
146           gtk_label_set_text(GTK_LABEL(InfoDImgRotation), "180");
147           break;
148       case -90:
149       case 270:
150           gtk_label_set_text(GTK_LABEL(InfoDImgRotation), "-90");
151           break;
152       default:
153           gtk_label_set_text(GTK_LABEL(InfoDImgRotation), "  0");
154           break;
155     }
156 
157     /* Update the flags buttons */
158     flags = gCurImage->noteFlags;
159     for (i=0, mask=1; i<10; ++i, mask <<= 1)
160         SetInfoDialogToggle(i, (flags & mask) != 0);
161 
162     /* Loop over the various EXIF elements.
163      * Expect we already called ExifReadInfo, back in LoadImageFromFile.
164      */
165     if (HasExif(gCurImage))
166         gtk_widget_set_sensitive(InfoExifContainer, TRUE);
167     else
168         gtk_widget_set_sensitive(InfoExifContainer, FALSE);
169     for (i=0; i<NUM_EXIF_FIELDS; ++i)
170     {
171         if (HasExif()) {
172             gtk_entry_set_text(GTK_ENTRY(InfoExifEntries[i]),
173                                ExifGetString(i));
174             gtk_entry_set_editable(GTK_ENTRY(InfoExifEntries[i]), FALSE);
175         }
176         else {
177             gtk_entry_set_text(GTK_ENTRY(InfoExifEntries[i]), " ");
178             gtk_entry_set_editable(GTK_ENTRY(InfoExifEntries[i]), FALSE);
179         }
180     }
181 }
182 
InfoDialogExpose(GtkWidget * widget,GdkEventKey * event)183 static gint InfoDialogExpose(GtkWidget* widget, GdkEventKey* event)
184 {
185     gtk_signal_handler_block_by_func(GTK_OBJECT(InfoDialog),
186                                      (GtkSignalFunc)InfoDialogExpose, 0);
187     gtk_widget_grab_focus(InfoDEntry);
188     if (gDebug)
189         printf("InfoDialogExpose\n");
190     UpdateInfoDialog(gCurImage);
191     /*
192     gtk_signal_handler_unblock_by_func(GTK_OBJECT(InfoDialog),
193                                        (GtkSignalFunc)InfoDialogExpose, 0);
194      */
195     /* Return FALSE so that the regular dialog expose handler will
196      * draw the dialog properly the first time.
197      */
198     return FALSE;
199 }
200 
HandleInfoKeyPress(GtkWidget * widget,GdkEventKey * event)201 static gint HandleInfoKeyPress(GtkWidget* widget, GdkEventKey* event)
202 {
203     switch (event->keyval)
204     {
205       case GDK_Escape:
206       case GDK_Return:
207       case GDK_KP_Enter:
208           PopdownInfoDialog();
209           return TRUE;
210     }
211 
212     /* handle other events iff Shift is pressed */
213     if (! event->state & GDK_SHIFT_MASK)
214         return FALSE;
215 
216     return HandleGlobalKeys(widget, event);
217 }
218 
219 /* Show a dialog with info about the current image.
220  */
ToggleInfo()221 void ToggleInfo()
222 {
223     GtkWidget *ok;
224     GtkWidget *label, *vbox, *box;
225 #ifdef SCROLLER
226     GtkWidget *scroller;
227 #endif
228     int i;
229 
230     if (gDebug) printf("ToggleInfo\n");
231 
232     if (InfoDialog && InfoDialog->window)
233     {
234         if (GTK_WIDGET_FLAGS(InfoDialog) & GTK_VISIBLE)
235             gtk_widget_hide(InfoDialog);
236         else {
237             UpdateInfoDialog(gCurImage);
238             gtk_widget_show(InfoDialog);
239         }
240         return;
241     }
242 
243     /* Else it's the first time, and we need to create the dialog */
244 
245     InfoDialog = gtk_dialog_new();
246     gtk_signal_connect(GTK_OBJECT(InfoDialog), "key_press_event",
247                        (GtkSignalFunc)HandleInfoKeyPress, 0);
248 
249 #ifdef SCROLLER
250     /* With the scroller, the dialog comes up tiny.
251      * Until I solve this, turn it off by default.
252      */
253     scroller = gtk_scrolled_window_new(0, 0);
254     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
255                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
256     gtk_box_pack_start(GTK_BOX (GTK_DIALOG(InfoDialog)->vbox), scroller,
257                        TRUE, TRUE, 0);
258     gtk_widget_show (scroller);
259 
260     vbox = gtk_vbox_new(FALSE, 3);
261     gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW (scroller),
262                                           vbox);
263     gtk_widget_show(vbox);
264 #else /* SCROLLER */
265     vbox = GTK_DIALOG(InfoDialog)->vbox;
266 #endif /* SCROLLER */
267 
268     gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
269 
270     /* Make the button */
271     ok = gtk_button_new_with_label("Ok");
272     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(InfoDialog)->action_area),
273                        ok, TRUE, TRUE, 0);
274     gtk_signal_connect(GTK_OBJECT(ok), "clicked",
275                        (GtkSignalFunc)PopdownInfoDialog, 0);
276     gtk_widget_show(ok);
277 
278     /* Add the info items */
279     InfoDImgName = gtk_label_new("imgName");
280     gtk_box_pack_start(GTK_BOX(vbox), InfoDImgName, TRUE, TRUE, 4);
281     gtk_widget_show(InfoDImgName);
282 
283     box = gtk_table_new(3, 2, FALSE);
284     gtk_table_set_col_spacings(GTK_TABLE(box), 7);
285     label = gtk_label_new("Displayed Size:");
286     gtk_misc_set_alignment(GTK_MISC(label), 1., .5);
287     gtk_table_attach_defaults(GTK_TABLE(box), label, 0, 1, 0, 1);
288     gtk_widget_show(label);
289     InfoDImgSize = gtk_label_new("0x0");
290     gtk_misc_set_alignment(GTK_MISC(InfoDImgSize), 0., .5);
291     gtk_table_attach_defaults(GTK_TABLE(box), InfoDImgSize, 1, 2, 0, 1);
292     gtk_widget_show(InfoDImgSize);
293     label = gtk_label_new("Actual Size:");
294     gtk_misc_set_alignment(GTK_MISC(label), 1., .5);
295     gtk_table_attach_defaults(GTK_TABLE(box), label, 0, 1, 1, 2);
296     gtk_widget_show(label);
297     InfoDOrigSize = gtk_label_new("0x0");
298     gtk_misc_set_alignment(GTK_MISC(InfoDOrigSize), 0., .5);
299     gtk_table_attach_defaults(GTK_TABLE(box), InfoDOrigSize, 1, 2, 1, 2);
300     gtk_widget_show(InfoDOrigSize);
301 
302     label = gtk_label_new("Rotation:");
303     gtk_misc_set_alignment(GTK_MISC(label), 1., .5);
304     gtk_table_attach_defaults(GTK_TABLE(box), label, 0, 1, 2, 3);
305     gtk_widget_show(label);
306     InfoDImgRotation = gtk_label_new("imgRot");
307     gtk_misc_set_alignment(GTK_MISC(InfoDImgRotation), 0., .5);
308     gtk_table_attach_defaults(GTK_TABLE(box), InfoDImgRotation, 1, 2, 2, 3);
309     gtk_widget_show(InfoDImgRotation);
310     gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, TRUE, 0);
311     gtk_widget_show(box);
312 
313     /* Make the line of Notes buttons */
314     box = gtk_table_new(2, 11, FALSE);
315     gtk_table_set_row_spacings(GTK_TABLE(box), 2);
316     gtk_table_set_col_spacings(GTK_TABLE(box), 7);
317     label = gtk_label_new("Notes:");
318     gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_RIGHT);
319     gtk_table_attach_defaults(GTK_TABLE(box), label, 0, 1, 0, 1);
320     gtk_widget_show(label);
321     for (i=0; i<10; ++i)
322     {
323         char str[2] = { '\0', '\0' };
324         str[0] = i + '0';
325         InfoFlag[i] = gtk_toggle_button_new_with_label(str);
326         gtk_table_attach_defaults(GTK_TABLE(box), InfoFlag[i], i+1, i+2, 0, 1);
327         gtk_widget_show(InfoFlag[i]);
328     }
329 
330     label = gtk_label_new("Comment:");
331     gtk_table_attach_defaults(GTK_TABLE(box), label, 0, 1, 1, 2);
332     gtk_widget_show(label);
333     InfoDEntry = gtk_entry_new();
334     gtk_table_attach_defaults(GTK_TABLE(box), InfoDEntry, 1, 11, 1, 2);
335     gtk_widget_show(InfoDEntry);
336     gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, TRUE, 0);
337     gtk_widget_show(box);
338 
339     label = gtk_hseparator_new();
340     gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 8);
341     gtk_widget_show(label);
342 
343     label = gtk_label_new("Exif:");
344     gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
345     gtk_widget_show(label);
346 
347     InfoExifContainer = gtk_table_new(NUM_EXIF_FIELDS, 2, FALSE);
348     /* set_padding doesn't work on tables, apparently */
349     /*gtk_misc_set_padding(GTK_MISC(InfoExifContainer), 7, 7);*/
350     gtk_table_set_row_spacings(GTK_TABLE(InfoExifContainer), 2);
351     gtk_table_set_col_spacings(GTK_TABLE(InfoExifContainer), 5);
352     gtk_box_pack_start(GTK_BOX(vbox), InfoExifContainer, TRUE, TRUE, 0);
353 
354     /* Loop over the various EXIF elements */
355     for (i=0; i<NUM_EXIF_FIELDS; ++i)
356     {
357         label = gtk_label_new(ExifLabels[i].str);
358         gtk_misc_set_alignment(GTK_MISC(label), 1., .5);
359         gtk_widget_show(label);
360         gtk_table_attach_defaults(GTK_TABLE(InfoExifContainer), label,
361                                   0, 1, i, i+1);
362         InfoExifEntries[i] = gtk_entry_new();
363         gtk_table_attach_defaults(GTK_TABLE(InfoExifContainer),
364                                   InfoExifEntries[i],
365                                   1, 2, i, i+1);
366         gtk_widget_show(InfoExifEntries[i]);
367     }
368     gtk_widget_show(InfoExifContainer);
369 
370     gtk_signal_connect(GTK_OBJECT(InfoDialog), "expose_event",
371                        (GtkSignalFunc)InfoDialogExpose, 0);
372 
373     gtk_widget_show(InfoDialog);
374     /* Don't call UpdateInfoDialog: it won't actually update
375      * everything it needs until after the first expose.
376      */
377     UpdateInfoDialog(gCurImage);
378 }
379 
380 /*
381  * A generic Prompt dialog.
382  */
383 
384 static GtkWidget* promptDialog = 0;
385 
386 static char* defaultYesChars = "yY\n";
387 static char* defaultNoChars = "nN";   /* ESC always works to cancel */
388 static char* gYesChars = 0;
389 static char* gNoChars = 0;
390 
391 static gint
HandlePromptKeyPress(GtkWidget * widget,GdkEventKey * event)392 HandlePromptKeyPress(GtkWidget* widget, GdkEventKey* event)
393 {
394     char c;
395 
396     if (event->keyval == GDK_Escape)
397     {
398         gtk_dialog_response(GTK_DIALOG(promptDialog), 0);
399         return TRUE;
400     }
401 
402     if (event->keyval == GDK_space)
403         c = ' ';
404 
405     else if (event->keyval >= GDK_A && event->keyval <= GDK_Z)
406         c = event->keyval - GDK_A + 'A';
407 
408     else if (event->keyval >= GDK_a && event->keyval <= GDK_z)
409         c = event->keyval - GDK_a + 'a';
410 
411     else if (event->keyval >= GDK_0 && event->keyval <= GDK_9)
412         c = event->keyval - GDK_0 + '0';
413 
414     else {
415         gdk_beep();
416         return FALSE;
417     }
418 
419     /* Now we have a c: see if it's in the yes or no char lists */
420     if (strchr(gYesChars, c))
421     {
422         gtk_dialog_response(GTK_DIALOG(promptDialog), 1);
423         return TRUE;
424     }
425     else if (strchr(gNoChars, c))
426     {
427         gtk_dialog_response(GTK_DIALOG(promptDialog), 0);
428         return TRUE;
429     }
430 
431     gdk_beep();
432     return FALSE;
433 }
434 
Prompt(char * msg,char * yesStr,char * noStr,char * yesChars,char * noChars)435 int Prompt(char* msg, char* yesStr, char* noStr, char* yesChars, char* noChars)
436 {
437     static GtkWidget* question = 0;
438     static GtkWidget* yesBtn = 0;
439     static GtkWidget* noBtn = 0;
440     int qYesNo;
441 
442     if (!yesStr)
443         yesStr = "OK";
444 
445     gYesChars = yesChars ? yesChars : defaultYesChars;
446     gNoChars = noChars ? noChars : defaultNoChars;
447 
448     if (!promptDialog)
449     {
450         /* First time through: make the dialog */
451         promptDialog = gtk_dialog_new_with_buttons("Question",
452                                                    GTK_WINDOW(gWin),
453                                                    GTK_DIALOG_MODAL,
454                                                    NULL);
455         KeepOnTop(promptDialog);
456 
457         /* Create the buttons manually, so we'll have their handles: */
458         yesBtn = gtk_dialog_add_button((GtkDialog*)promptDialog,
459                                        GTK_STOCK_OK, 1);
460         noBtn = gtk_dialog_add_button((GtkDialog*)promptDialog,
461                                        GTK_STOCK_CANCEL, 0);
462 
463         /* Make sure Enter will activate OK, not Cancel
464         gtk_dialog_set_default_response(GTK_DIALOG(promptDialog),
465                                         GTK_RESPONSE_OK);
466          */
467 
468         gtk_signal_connect(GTK_OBJECT(promptDialog), "key_press_event",
469                            (GtkSignalFunc)HandlePromptKeyPress, 0);
470 
471         /* Make the label: */
472         question = gtk_label_new(msg);
473         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(promptDialog)->vbox),
474                            question, TRUE, TRUE, 15);
475         gtk_widget_show(question);
476     }
477     else
478         gtk_label_set_text(GTK_LABEL(question), msg);
479 
480     /* Set the button labels and show/hide the buttons requested.
481      * We always have a Yes button, but No/Cancel is optional.
482      */
483     if (yesStr) {
484         gtk_button_set_label(GTK_BUTTON(yesBtn), yesStr);
485         gtk_widget_show(yesBtn);
486     }
487     else {
488         gtk_widget_hide(yesBtn);
489     }
490     if (noStr) {
491         gtk_button_set_label(GTK_BUTTON(noBtn), noStr);
492         gtk_widget_show(noBtn);
493     }
494     else {
495         gtk_widget_hide(noBtn);
496     }
497 
498     gtk_widget_show(promptDialog);
499 
500     qYesNo = gtk_dialog_run(GTK_DIALOG(promptDialog));
501 
502     gtk_widget_hide(promptDialog);
503     return qYesNo;
504 }
505 
SetNewFiles(GtkWidget * dialog,gint res)506 static void SetNewFiles(GtkWidget *dialog, gint res)
507 {
508 	GSList *files, *cur;
509     gboolean overwrite;
510 
511     if (res == GTK_RESPONSE_ACCEPT)
512         overwrite = FALSE;
513     else if (res == GTK_RESPONSE_OK)
514         overwrite = TRUE;
515     else {
516         gtk_widget_destroy (dialog);
517         return;
518     }
519 
520     files = cur = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
521 
522     if (overwrite)
523         ClearImageList();
524 
525     gCurImage = 0;
526 
527     while (cur)
528     {
529         PhoImage* img = AddImage((char*)(cur->data));
530         if (!gCurImage)
531             gCurImage = img;
532 
533         cur = cur->next;
534     }
535     if (files)
536         g_slist_free (files);
537 
538     gtk_widget_destroy (dialog);
539 
540     ThisImage();
541 }
542 
ChangeWorkingFileSet()543 void ChangeWorkingFileSet()
544 {
545     GtkWidget* fsd = gtk_file_chooser_dialog_new("Change file set", NULL,
546                                          GTK_FILE_CHOOSER_ACTION_OPEN,
547                                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
548                                          GTK_STOCK_ADD, GTK_RESPONSE_ACCEPT,
549                                          GTK_STOCK_NEW, GTK_RESPONSE_OK,
550                                          NULL);
551     gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(fsd), TRUE);
552 
553     if (gCurImage && gCurImage->filename && gCurImage->filename[0] == '/')
554         gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fsd),
555                                             g_dirname(gCurImage->filename));
556     else {
557         char buf[BUFSIZ];
558         gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fsd),
559                                             getcwd(buf, BUFSIZ));
560     }
561 
562     g_signal_connect(G_OBJECT(fsd), "response", G_CALLBACK(SetNewFiles), 0);
563     gtk_widget_show(fsd);
564 }
565 
566 
567 
568