1 /*
2 * imagenote.c: save info about images. For pho, an image viewer.
3 *
4 * Copyright 2002,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
10 #include <string.h>
11 #include <stdio.h>
12 #include <stdlib.h> /* for malloc() */
13 #include <glib.h>
14 #include <ctype.h>
15 #include <fcntl.h>
16 #include <unistd.h> /* for write() */
17
18 static char *sFlagFileList[NUM_NOTES];
19
InitNotes()20 void InitNotes()
21 {
22 int i;
23 for (i=0; i<NUM_NOTES; ++i)
24 sFlagFileList[i] = 0;
25 }
26
ToggleNoteFlag(PhoImage * img,int note)27 void ToggleNoteFlag(PhoImage* img, int note)
28 {
29 int bit = (1 << note);
30 if (img->noteFlags & bit)
31 img->noteFlags &= ~bit;
32 else
33 img->noteFlags |= bit;
34
35 /* Update any dialogs which might be showing toggles */
36 SetInfoDialogToggle(note, (img->noteFlags & bit) != 0);
37 SetKeywordsDialogToggle(note, (img->noteFlags & bit) != 0);
38 }
39
40 /* Guard against filenames which contain odd characters, like
41 * spaces or quotes. Returns allocated memory.
42 */
QuoteString(char * str)43 char *QuoteString(char *str)
44 {
45 int i;
46 char *newstr;
47
48 /* look for a space or quote in str */
49 for (i = 0; str[i] != '\0'; i++)
50 if (isspace(str[i]) || (str[i] == '\"') || (str[i] == '\'')) {
51 GString *gstr = g_string_new("\"");
52 for (i = 0; str[i] != '\0'; i++) {
53 if (str[i] == '\"')
54 g_string_append(gstr, (gchar *)"\\\"");
55 /*else if (str[i] == '\'')
56 g_string_append(gstr, (gchar *)"\\\'");*/
57 else
58 g_string_append_c(gstr, (gchar)str[i]);
59 }
60
61 g_string_append_c(gstr, '\"');
62
63 newstr = gstr->str;
64 g_string_free(gstr, FALSE);
65 return newstr;
66 }
67
68 /* If there are no spaces or quotes in str, return a copy of str */
69 return (char *)g_strdup((gchar *)str);
70 }
71
AddImgToList(char ** strp,char * str)72 void AddImgToList(char** strp, char* str)
73 {
74 str = QuoteString(str);
75
76 if (*strp)
77 {
78 int newsize = strlen(*strp) + strlen(str) + 2;
79 char* newstr = malloc(newsize);
80 if (!newstr) {
81 free(str);
82 *strp = 0;
83 return;
84 }
85 snprintf(newstr, newsize, "%s %s", *strp, str);
86 free(*strp);
87 *strp = newstr;
88 }
89 else
90 *strp = strdup(str);
91
92 /* QuoteString allocated a copy, so free that now: */
93 free(str);
94 }
95
96
97 /* Captions:
98 * captions may be stored in one file per image, or they may
99 * all be in one file in lines like
100 * imgname: caption
101 * This is controlled by gCapFileFormat: if it has a %s in it,
102 * that will be replaced by the current image file name.
103 */
104
GlobalCaptionFile()105 static int GlobalCaptionFile()
106 {
107 char* cp;
108
109 /* For captions, are we going to put them all in one file,
110 * or in individual files? That's controlled by whether
111 * gCapFileFormat includes a '%s" in it, i.e., will it
112 * substitute the name of the image file inside caption filenames.
113 */
114 for (cp = gCapFileFormat; *cp != 0; ++cp) {
115 if (cp[0] == '%' && cp[1] == 's')
116 return 0;
117 }
118
119 /* If we got throught he loop without seeing %s, then the
120 * caption file is global.
121 */
122 return 1;
123 }
124
125 /* Return the appropriate caption file name for this image */
CapFileName(PhoImage * img)126 static char* CapFileName(PhoImage* img)
127 {
128 /* How much ram do we need for the caption filename? */
129 static char buf[BUFSIZ];
130 int caplength = snprintf(buf, BUFSIZ, gCapFileFormat, img->filename);
131 if (strncmp(img->filename, buf, caplength) == 0) {
132 fprintf(stderr,
133 "Caption filename expanded to same as image filename. Bailing!\n");
134 return 0;
135 }
136 return buf;
137 }
138
139 /* Read any caption that might be in the caption file.
140 * If the caption file is global, though, we read the file once
141 * for the first image and cache them.
142 */
ReadCaption(PhoImage * img)143 void ReadCaption(PhoImage* img)
144 {
145 int i;
146 int capfile;
147 char* capfilename;
148
149 static int sFirstTime = 1;
150 static int sGlobalCaptions = 0;
151 static char** sCaptionFileList = 0;
152 static char** sCaptionList = 0;
153 static int sNumCaptions = 0;
154
155 if (sFirstTime) {
156 sFirstTime = 0;
157 sGlobalCaptions = GlobalCaptionFile();
158 if (sGlobalCaptions) {
159 /* Read the global file */
160 char line[10000];
161 int numlines = 0;
162
163 FILE* capfile = fopen(gCapFileFormat, "r");
164
165 if (!capfile) /* No captions to read, nothing to do */
166 return;
167
168 /* Make a first pass through the file just to count lines */
169 while (fgets(line, sizeof line, capfile)) {
170 char* colon = strchr(line, ':');
171 if (colon)
172 ++numlines;
173 }
174
175 /* Now we can allocate space for the lists of files/captions */
176 sCaptionFileList = malloc(numlines * (sizeof (char*)));
177 sCaptionList = malloc(numlines * (sizeof (char*)));
178
179 /* and loop through again to actually read the captions */
180 rewind(capfile);
181 while (fgets(line, sizeof line, capfile)) {
182 char* cp;
183
184 /* Line should look like: imagename: blah blah */
185 char* colon = strchr(line, ':');
186 if (!colon) continue;
187
188 /* terminate the filename string */
189 *colon = '\0';
190 sCaptionFileList[sNumCaptions] = strdup(line);
191
192 /* Skip the colon and any spaces immediately after it */
193 ++colon;
194 while (*colon == ' ')
195 ++colon;
196 /* strip off the terminal newline */
197 for (cp = colon; *cp != '\0'; ++cp)
198 if (*cp == '\n' || *cp == '\r') {
199 *cp = '\0';
200 break;
201 }
202
203 /* Now colon points to the beginning of the caption */
204 sCaptionList[sNumCaptions] = strdup(colon);
205
206 ++sNumCaptions;
207 }
208 fclose(capfile);
209 }
210 }
211
212 /* Now we've done the first-time reading of the file, if needed. */
213 if (sGlobalCaptions) {
214 for (i=0; i < sNumCaptions; ++i)
215 if (!strcmp(sCaptionFileList[i], img->filename)) {
216 img->caption = sCaptionList[i];
217 return;
218 }
219 img->caption = 0;
220 return;
221 }
222
223 /* If we get here, caption files are per-image.
224 * So look for the appropriate caption file.
225 */
226
227 capfilename = CapFileName(img);
228 if (!capfilename)
229 return;
230
231 capfile = open(capfilename, O_RDONLY);
232 if (capfile < 0)
233 return;
234 printf("Reading caption from %s\n", capfilename);
235
236 #define MAX_CAPTION 1023
237 img->caption = calloc(1, MAX_CAPTION);
238 if (!(img->caption)) {
239 perror("Couldn't allocate memory for caption");
240 return;
241 }
242 read(capfile, img->caption, MAX_CAPTION-1);
243 for (i=0; i < MAX_CAPTION && img->caption[i] != 0; ++i) {
244 if (img->caption[i] == '\n') {
245 img->caption[i] = ' ';
246 }
247 }
248 close(capfile);
249 if (gDebug)
250 fprintf(stderr, "Read caption file %s\n", capfilename);
251 }
252
253 /* Finally, the routine that prints a summary to a file or stdout */
PrintNotes()254 void PrintNotes()
255 {
256 int i;
257 char *rot90=0, *rot180=0, *rot270=0, *rot0=0, *unmatchExif=0;
258 PhoImage *img;
259 FILE *capfile = 0;
260 int useGlobalCaptionFile = GlobalCaptionFile();
261
262 /* Should free up memory here, e.g. for sFlagFileList,
263 * but since this is only called right before exit,
264 * it's never been allocated so it doesn't matter.
265 * If PrintNotes ever gets called except at exit,
266 * this will leak.
267 */
268
269 img = gFirstImage;
270 while (img)
271 {
272 if (img->caption && img->caption[0] && gCapFileFormat) {
273 if (gDebug)
274 printf("Caption %s: %s\n", img->filename, img->caption);
275
276 /* If we have a global captions file and we haven't
277 * opened it yet, do so now.
278 */
279 if (useGlobalCaptionFile && gCapFileFormat && !capfile) {
280 capfile = fopen(gCapFileFormat, "w");
281 if (!capfile) {
282 perror(gCapFileFormat);
283 gCapFileFormat = 0; /* don't try again to write to it */
284 }
285 }
286
287 if (capfile) { /* One shared caption file */
288 /* capfile is already open, so append to it: */
289 fprintf(capfile, "%s: %s\n\n", img->filename, img->caption);
290
291 } else if (gCapFileFormat) { /* need individual caption files */
292 char* capname = CapFileName(img);
293 if (capname) {
294 capfile = fopen(capname, "w");
295 if (capfile) {
296 fputs(img->caption, capfile);
297 fclose(capfile);
298 capfile = 0;
299 }
300 else
301 perror(capname);
302 }
303 }
304 }
305 if (img->noteFlags)
306 {
307 int flag, j;
308 for (j=0, flag=1; j<NUM_NOTES; ++j, flag <<= 1)
309 if (img->noteFlags & flag)
310 AddImgToList(sFlagFileList+j, img->filename);
311 }
312
313 switch (img->curRot)
314 {
315 case 90:
316 AddImgToList(&rot90, img->filename);
317 break;
318 case 180:
319 AddImgToList(&rot180, img->filename);
320 break;
321 case 270:
322 case -90:
323 AddImgToList(&rot270, img->filename);
324 break;
325 case 0:
326 /* If the image is rotated at zero but the EXIF says otherwise,
327 * we need to list that too:
328 */
329 if (img->exifRot != 0)
330 AddImgToList(&rot0, img->filename);
331 break;
332 default:
333 break;
334 }
335
336 /* If the user-chosen rotation doesn't match the EXIF,
337 * we need to know that too.
338 */
339 if (img->curRot != img->exifRot)
340 AddImgToList(&unmatchExif, img->filename);
341
342 img = img->next;
343 /* Have we looped back to the beginning?*/
344 if (img == gFirstImage) break;
345 }
346
347 if (capfile)
348 fclose(capfile);
349
350 /* Now we've looped over all the structs, so we can print out
351 * the tables of rotation and notes.
352 */
353 if (rot90)
354 printf("\nRotate 90 (CW): %s\n", rot90);
355 if (rot270)
356 printf("\nRotate -90 (CCW): %s\n", rot270);
357 if (rot180)
358 printf("\nRotate 180: %s\n", rot180);
359 if (rot0)
360 printf("\nRotate 0 (wrong EXIF): %s\n", rot0);
361 if (unmatchExif)
362 printf("\nWrong EXIF: %s\n", unmatchExif);
363 for (i=0; i < NUM_NOTES; ++i)
364 if (sFlagFileList[i])
365 {
366 char* keyword = KeywordString(i);
367 if (keyword && *keyword)
368 printf("\n%s: ", keyword);
369 else
370 printf("\nNote %d: ", i);
371 printf("%s\n", sFlagFileList[i]);
372 }
373 printf("\n");
374 }
375