1 /*
2  * keydialog.c: the pho keywords dialog.
3  *
4  * Copyright 2007 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 
11 #include <gdk/gdkkeysyms.h>
12 #include <stdio.h>      /* needed on Mac, not on Linux, for sprintf */
13 #include <ctype.h>
14 #include <string.h>
15 #include <stdlib.h>     /* for malloc() and free() */
16 
17 static GtkWidget* KeywordsDialog = 0;
18 static GtkWidget* KeywordsCaption = 0;
19 static GtkWidget* KeywordsDEntry[NUM_NOTES] = {0};
20 static GtkWidget* KeywordsDToggle[NUM_NOTES] = {0};
21 static GtkWidget* KeywordsDImgName = 0;
22 static GtkWidget* KeywordsContainer = 0;  /* where the Entries live */
23 static PhoImage* sLastImage = 0;
24 
LeaveKeywordsMode()25 static void LeaveKeywordsMode()
26 {
27     SetViewModes(PHO_DISPLAY_NORMAL, PHO_SCALE_NORMAL, 1.0);
28 }
29 
ToggleKeywordsMode()30 void ToggleKeywordsMode()
31 {
32     if (gDisplayMode == PHO_DISPLAY_KEYWORDS)
33         LeaveKeywordsMode();
34     else {
35         SetViewModes(PHO_DISPLAY_KEYWORDS, PHO_SCALE_FIXED, 0.0);
36         ThisImage();
37     }
38 }
39 
40 /* Make sure we remember any changes that have been made in the dialog */
RememberKeywords()41 void RememberKeywords()
42 {
43     int i, mask, flags;
44 
45     if (!sLastImage)
46         return;
47 
48     flags = 0;
49     for (i=0, mask=1; i < NUM_NOTES; ++i, mask <<= 1)
50     {
51         if (KeywordsDToggle[i] &&
52             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(KeywordsDToggle[i])))
53             flags |= mask;
54     }
55 
56     sLastImage->noteFlags = flags;
57 
58     /* and save a caption, if any */
59     if (sLastImage->caption)
60         free(sLastImage->caption);
61     sLastImage->caption = strdup((char*)gtk_entry_get_text(
62                                      (GtkEntry*)KeywordsCaption));
63 }
64 
65 /* When deleting an image, we need to clear any notion of sLastImage
66  * or else we'll crash trying to access it.
67  */
NoCurrentKeywords()68 void NoCurrentKeywords()
69 {
70     sLastImage = 0;
71 }
72 
SetKeywordsDialogToggle(int which,int newval)73 void SetKeywordsDialogToggle(int which, int newval)
74 {
75     if (KeywordsDToggle[which])
76         gtk_toggle_button_set_active((GtkToggleButton*)KeywordsDToggle[which],
77                                      newval ? TRUE : FALSE);
78 }
79 
UpdateKeywordsDialog()80 void UpdateKeywordsDialog()
81 {
82     char buffer[256];
83     char* s;
84     int i, mask, flags;
85 
86     if (!gCurImage || !KeywordsDialog || gDisplayMode != PHO_DISPLAY_KEYWORDS)
87         return;
88     if (!IsVisible(KeywordsDialog)) return;
89     if (gCurImage != sLastImage)
90         sLastImage = gCurImage;
91 
92     sprintf(buffer, "pho keywords (%s)", gCurImage->filename);
93     gtk_window_set_title(GTK_WINDOW(KeywordsDialog), buffer);
94 
95     s = gCurImage->caption;
96     gtk_entry_set_text(GTK_ENTRY(KeywordsCaption), s ? s : "");
97 
98     gtk_label_set_text(GTK_LABEL(KeywordsDImgName), gCurImage->filename);
99 
100     /* Update the flags fields */
101     flags = gCurImage->noteFlags;
102     for (i=0, mask=1; i < NUM_NOTES; ++i, mask <<= 1)
103     {
104         if (KeywordsDToggle[i])
105             SetKeywordsDialogToggle(i, flags & mask);
106     }
107 }
108 
KeywordString(int notenum)109 char* KeywordString(int notenum)
110 {
111     if (! KeywordsDEntry[notenum])
112         return 0;
113     return (char*)gtk_entry_get_text((GtkEntry*)KeywordsDEntry[notenum]);
114 }
115 
116 static void AddNewKeywordField();
117 
118 /* When the user hits return in the last keyword field,
119  * add a new one, assuming we don't already have too many.
120  */
activate(GtkEntry * entry,int which)121 static void activate(GtkEntry *entry, int which)
122 {
123     if (which < NUM_NOTES-1 && KeywordsDEntry[which+1] == 0)
124         AddNewKeywordField();
125 }
126 
handleKeywordsKeyPress(GtkWidget * widget,GdkEventKey * event)127 static gint handleKeywordsKeyPress(GtkWidget* widget, GdkEventKey* event)
128 {
129     /* We only handle a few key events that aren't shifted: */
130     switch (event->keyval)
131     {
132       case GDK_Escape:
133           LeaveKeywordsMode();
134           return TRUE;
135     }
136 
137     /* But we handle certain other events if a modifier key is down */
138     if (! (event->state & GDK_MODIFIER_MASK))
139         return FALSE;
140 
141     /* But don't just pass all modifier events -- many of them are
142      * meaningful when editing a keyword in a text field!
143      */
144     /* Shifted printable keys should probably stay here */
145     if (event->state & GDK_SHIFT_MASK && (event->length > 0)
146         && isprint(event->string[0]))
147         return FALSE;
148     /* Emacs/readline editing keys are also useful */
149     if (event->state & GDK_CONTROL_MASK) {
150         switch (event->keyval)
151         {
152         case GDK_a:
153         case GDK_e:
154         case GDK_u:
155         case GDK_h:
156         case GDK_w:
157         case GDK_k:
158         case GDK_d:
159             return FALSE;
160         }
161     }
162 
163     /* Otherwise, it's probably a pho key binding, so pass it on: */
164     return HandleGlobalKeys(widget, event);
165 }
166 
167 /* Add a new keyword field to the dialog */
AddNewKeywordField()168 static void AddNewKeywordField()
169 {
170     long i;
171     GtkWidget* label;
172     char buf[BUFSIZ];
173 
174     for (i=0; i<NUM_NOTES; ++i)
175     {
176         if (KeywordsDEntry[i] == 0)
177         {
178             GtkWidget* hbox = gtk_hbox_new(FALSE, 3);
179             gtk_box_pack_start(GTK_BOX(KeywordsContainer), hbox,
180                                TRUE, TRUE, 4);
181 
182             sprintf(buf, "%-2ld", i);
183             KeywordsDToggle[i] = gtk_toggle_button_new_with_label(buf);
184             gtk_box_pack_start(GTK_BOX(hbox), KeywordsDToggle[i],
185                                FALSE, FALSE, 4);
186             gtk_toggle_button_set_active((GtkToggleButton*)KeywordsDToggle[i],
187                                          TRUE);
188             gtk_widget_show(KeywordsDToggle[i]);
189 
190             KeywordsDEntry[i] = gtk_entry_new();
191             gtk_box_pack_start(GTK_BOX(hbox), KeywordsDEntry[i],
192                                TRUE, TRUE, 4);
193             gtk_signal_connect(GTK_OBJECT(KeywordsDEntry[i]), "activate",
194                                (GtkSignalFunc)activate, (gpointer)i);
195             gtk_widget_show(KeywordsDEntry[i]);
196             gtk_widget_show(hbox);
197 
198             gtk_widget_grab_focus(KeywordsDEntry[i]);
199             return;
200         }
201     }
202 
203     /* If we got here, we've overflowed */
204     sprintf(buf, "That's all: sorry, only %ld keywords at once", NUM_NOTES);
205     label = gtk_label_new(buf);
206     gtk_box_pack_start(GTK_BOX(KeywordsContainer), label, TRUE, TRUE, 4);
207     gtk_widget_show(label);
208 }
209 
MakeNewKeywordsDialog()210 static void MakeNewKeywordsDialog()
211 {
212     GtkWidget *ok, *label;
213     GtkWidget *dlg_vbox, *sep, *btn_box, *hbox;
214     int i;
215 
216     /* Use a toplevel window, so it won't pop up centered on the image win */
217     KeywordsDialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
218     /* Unfortunately that means we have to make everything inside. */
219 
220     /* Tried making it a utility win for focus issues, but it didn't help:
221     gtk_window_set_type_hint (GTK_WINDOW(KeywordsDialog),
222                               GDK_WINDOW_TYPE_HINT_UTILITY);
223      */
224 
225     dlg_vbox = gtk_vbox_new(FALSE, 3);
226     gtk_container_add(GTK_CONTAINER(KeywordsDialog), dlg_vbox);
227     gtk_widget_show(dlg_vbox);
228 
229     gtk_signal_connect(GTK_OBJECT(KeywordsDialog), "key_press_event",
230                        (GtkSignalFunc)handleKeywordsKeyPress, 0);
231 
232     KeywordsContainer = gtk_vbox_new(FALSE, 3);
233     gtk_container_add(GTK_CONTAINER(dlg_vbox), KeywordsContainer);
234     gtk_widget_show(KeywordsContainer);
235     sep = gtk_hseparator_new();
236     gtk_container_add(GTK_CONTAINER(dlg_vbox), sep);
237     gtk_widget_show(sep);
238     btn_box = gtk_hbox_new(FALSE, 3);
239     gtk_container_add(GTK_CONTAINER(dlg_vbox), btn_box);
240     gtk_container_set_border_width(GTK_CONTAINER(btn_box), 20);
241     gtk_widget_show(btn_box);
242 
243     gtk_container_set_border_width(GTK_CONTAINER(KeywordsContainer), 8);
244 
245     /* Make the button */
246     ok = gtk_button_new_with_label("Leave Keywords Mode");
247     gtk_box_pack_start(GTK_BOX(btn_box), ok, TRUE, TRUE, 0);
248 
249     gtk_signal_connect(GTK_OBJECT(ok), "clicked",
250                        (GtkSignalFunc)LeaveKeywordsMode, 0);
251     gtk_widget_show(ok);
252 
253     KeywordsDImgName = gtk_label_new("imgName");
254     gtk_box_pack_start(GTK_BOX(KeywordsContainer), KeywordsDImgName,
255                        TRUE, TRUE, 4);
256     gtk_widget_show(KeywordsDImgName);
257 
258     label = gtk_label_new("To add a new keyword, hit Enter in the last box:");
259     gtk_box_pack_start(GTK_BOX(KeywordsContainer), label, TRUE, TRUE, 4);
260     gtk_widget_show(label);
261 
262     /* Add the caption field */
263     hbox = gtk_hbox_new(FALSE, 3);
264     gtk_box_pack_start(GTK_BOX(KeywordsContainer), hbox,
265                        TRUE, TRUE, 4);
266     label = gtk_label_new("Caption:");
267     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 4);
268     gtk_widget_show(label);
269 
270     KeywordsCaption = gtk_entry_new();
271     gtk_box_pack_start(GTK_BOX(hbox), KeywordsCaption, TRUE, TRUE, 4);
272     gtk_widget_show(KeywordsCaption);
273 
274     gtk_widget_show(hbox);
275 
276     /* Make sure all the entries are initialized */
277     for (i=0; i < NUM_NOTES; ++i)
278         KeywordsDEntry[i] = 0;
279 
280     /* Add the first keywords field. Others will be added as needed */
281     AddNewKeywordField();
282 
283     gtk_widget_show(KeywordsDialog);
284 }
285 
286 /* Make sure the dialog is showing */
ShowKeywordsDialog()287 void ShowKeywordsDialog()
288 {
289     if (!gWin)
290         return;
291 
292     if (!KeywordsDialog)
293         MakeNewKeywordsDialog();
294 
295     else if (!IsVisible(KeywordsDialog))
296         gtk_widget_show(KeywordsDialog);
297     /* else it's already showing */
298 
299     /* Calling this from UpdateKeywordsDialog somehow sends focus
300      * back to the image window.
301     KeepOnTop(KeywordsDialog);
302      */
303     /*
304     gdk_window_raise(GTK_WIDGET(KeywordsDialog)->window);
305      * XXX Why does raise raise forever? Isn't there some way
306      * to raise once but still let the user control the stacking??
307      */
308 
309     /* Save any state we have from the previous image */
310     RememberKeywords();
311 
312     /* update to show the state of the new image */
313     UpdateKeywordsDialog();
314 }
315 
HideKeywordsDialog()316 void HideKeywordsDialog()
317 {
318     RememberKeywords();
319     if (IsVisible(KeywordsDialog))
320         gtk_widget_hide(KeywordsDialog);
321     sLastImage = 0;
322 }
323 
324