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