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