1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3 *
4 * Copyright (C) 1997-2013 Stuart Parmenter and others,
5 * See the file AUTHORS for a list.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 * 02111-1307, USA.
21 */
22
23 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
24 # include "config.h"
25 #endif /* HAVE_CONFIG_H */
26 #include "address.h"
27
28 #include <string.h>
29 #include <gmime/gmime.h>
30
31 #include "misc.h"
32 #include <glib/gi18n.h>
33
34 static GObjectClass *parent_class;
35
36 static void libbalsa_address_class_init(LibBalsaAddressClass * klass);
37 static void libbalsa_address_init(LibBalsaAddress * ab);
38 static void libbalsa_address_finalize(GObject * object);
39
40 static gchar ** vcard_strsplit(const gchar * string);
41
libbalsa_address_get_type(void)42 GType libbalsa_address_get_type(void)
43 {
44 static GType address_type = 0;
45
46 if (!address_type) {
47 static const GTypeInfo address_info = {
48 sizeof(LibBalsaAddressClass),
49 NULL, /* base_init */
50 NULL, /* base_finalize */
51 (GClassInitFunc) libbalsa_address_class_init,
52 NULL, /* class_finalize */
53 NULL, /* class_data */
54 sizeof(LibBalsaAddress),
55 0, /* n_preallocs */
56 (GInstanceInitFunc) libbalsa_address_init
57 };
58
59 address_type =
60 g_type_register_static(G_TYPE_OBJECT,
61 "LibBalsaAddress",
62 &address_info, 0);
63 }
64
65 return address_type;
66 }
67
68 static void
libbalsa_address_class_init(LibBalsaAddressClass * klass)69 libbalsa_address_class_init(LibBalsaAddressClass * klass)
70 {
71 GObjectClass *object_class;
72
73 parent_class = g_type_class_peek_parent(klass);
74
75 object_class = G_OBJECT_CLASS(klass);
76 object_class->finalize = libbalsa_address_finalize;
77 }
78
79 static void
libbalsa_address_init(LibBalsaAddress * addr)80 libbalsa_address_init(LibBalsaAddress * addr)
81 {
82 addr->nick_name = NULL;
83 addr->full_name = NULL;
84 addr->first_name = NULL;
85 addr->last_name = NULL;
86 addr->organization = NULL;
87 addr->address_list = NULL;
88 }
89
90 static void
libbalsa_address_finalize(GObject * object)91 libbalsa_address_finalize(GObject * object)
92 {
93 LibBalsaAddress *addr;
94
95 g_return_if_fail(object != NULL);
96
97 addr = LIBBALSA_ADDRESS(object);
98
99 g_free(addr->nick_name); addr->nick_name = NULL;
100 g_free(addr->full_name); addr->full_name = NULL;
101 g_free(addr->first_name); addr->first_name = NULL;
102 g_free(addr->last_name); addr->last_name = NULL;
103 g_free(addr->organization); addr->organization = NULL;
104
105 g_list_foreach(addr->address_list, (GFunc) g_free, NULL);
106 g_list_free(addr->address_list);
107 addr->address_list = NULL;
108
109 G_OBJECT_CLASS(parent_class)->finalize(object);
110 }
111
112 LibBalsaAddress *
libbalsa_address_new(void)113 libbalsa_address_new(void)
114 {
115 return g_object_new(LIBBALSA_TYPE_ADDRESS, NULL);
116 }
117
118 /** Extract full name in order from <string> that has GnomeCard format
119 and returns the pointer to the allocated memory chunk.
120
121 VCARD code attempts to obey published documentation:
122
123 [1] VCARD 1.2 specs: http://www.imc.org/pdi/vcard-21.txt
124 [2] VCARD 3.0 specs, RFC 2426 (http://www.ietf.org/rfc/rfc2426.txt)
125 */
126 gchar*
libbalsa_address_extract_name(const gchar * string,gchar ** last_name,gchar ** first_name)127 libbalsa_address_extract_name(const gchar * string, gchar ** last_name,
128 gchar ** first_name)
129 {
130 enum GCardFieldOrder { LAST = 0, FIRST, MIDDLE, PREFIX, SUFFIX };
131 gint cpt, j;
132 gchar **fld, **name_arr;
133 gchar *res = NULL;
134
135 fld = vcard_strsplit(string);
136
137 cpt = 0;
138 while (fld[cpt] != NULL)
139 cpt++;
140
141 if (cpt == 0) {
142 /* insane empty name */
143 g_strfreev(fld);
144 return NULL;
145 }
146
147 if (fld[LAST] && *fld[LAST])
148 *last_name = g_strdup(fld[LAST]);
149
150 if (fld[FIRST] && *fld[FIRST])
151 *first_name = fld[MIDDLE] && *fld[MIDDLE] ?
152 g_strconcat(fld[FIRST], " ", fld[MIDDLE], NULL) :
153 g_strdup(fld[FIRST]);
154
155 name_arr = g_malloc((cpt + 1) * sizeof(gchar *));
156
157 j = 0;
158 if (cpt > PREFIX && *fld[PREFIX] != '\0')
159 name_arr[j++] = g_strdup(fld[PREFIX]);
160
161 if (cpt > FIRST && *fld[FIRST] != '\0')
162 name_arr[j++] = g_strdup(fld[FIRST]);
163
164 if (cpt > MIDDLE && *fld[MIDDLE] != '\0')
165 name_arr[j++] = g_strdup(fld[MIDDLE]);
166
167 if (cpt > LAST && *fld[LAST] != '\0')
168 name_arr[j++] = g_strdup(fld[LAST]);
169
170 if (cpt > SUFFIX && *fld[SUFFIX] != '\0')
171 name_arr[j++] = g_strdup(fld[SUFFIX]);
172
173 name_arr[j] = NULL;
174
175 g_strfreev(fld);
176
177 /* collect the data to one string */
178 res = g_strjoinv(" ", name_arr);
179 g_strfreev(name_arr);
180
181 return res;
182 }
183
184 /* Duplicates some code from address-book-vcard.c:
185 */
186 static gchar*
validate_vcard_string(gchar * vcstr,const gchar * charset)187 validate_vcard_string(gchar * vcstr, const gchar * charset)
188 {
189 gchar * utf8res;
190 gsize b_written;
191
192 /* check if it's a utf8 clean string and return it in this case */
193 if (!vcstr || g_utf8_validate(vcstr, -1, NULL))
194 return vcstr;
195
196 /* convert from the passed charset or as fallback from the locale setting */
197 if (charset && g_ascii_strcasecmp(charset, "utf-8")) {
198 utf8res = g_convert(vcstr, -1, "utf-8", charset, NULL, &b_written, NULL);
199 } else
200 utf8res = g_locale_to_utf8(vcstr, -1, NULL, &b_written, NULL);
201 if (!utf8res)
202 return vcstr;
203
204 g_free(vcstr);
205 return utf8res;
206 }
207
208
209 static inline gchar *
vcard_qp_decode(gchar * str)210 vcard_qp_decode(gchar * str)
211 {
212 gint len = strlen(str);
213 gchar * newstr = g_malloc0(len + 1);
214 int state = 0;
215 guint32 save;
216
217 /* qp decode the input string */
218 g_mime_encoding_quoted_decode_step((unsigned char *) str, len,
219 (unsigned char *) newstr, &state, &save);
220
221 /* free input and return new string */
222 g_free(str);
223 return newstr;
224 }
225
226
227 static inline gchar *
vcard_b64_decode(gchar * str)228 vcard_b64_decode(gchar * str)
229 {
230 gint len = strlen(str);
231 gchar * newstr = g_malloc0(len + 1);
232 int state = 0;
233 guint32 save;
234
235 /* base64 decode the input string */
236 g_mime_encoding_base64_decode_step((unsigned char *) str, len,
237 (unsigned char *) newstr, &state, &save);
238
239 /* free input and return new string */
240 g_free(str);
241 return newstr;
242 }
243
244
245 static inline gchar *
vcard_charset_to_utf8(gchar * str,const gchar * charset)246 vcard_charset_to_utf8(gchar * str, const gchar * charset)
247 {
248 gsize bytes_written;
249 gchar * convstr;
250
251 /* convert only if the source is known and not utf-8 */
252 if (!charset || !g_ascii_strcasecmp(charset, "utf-8"))
253 return str;
254
255 convstr = g_convert(str, -1, "utf-8", charset, NULL, &bytes_written, NULL);
256 g_free(str);
257 return convstr ? convstr : g_strdup("");
258 }
259
260
261 /* mainly copied from g_strsplit, but (a) with the fixed delimiter ';'
262 * (b) ignoring '\;' sequences (c) always returning as many elements as
263 * possible and (d) unescape '\;' sequences in the resulting array */
264 static gchar **
vcard_strsplit(const gchar * string)265 vcard_strsplit(const gchar * string)
266 {
267 GSList *string_list = NULL, *slist;
268 gchar **str_array, *s;
269 guint n = 0;
270 const gchar *remainder;
271
272 g_return_val_if_fail(string != NULL, NULL);
273
274 remainder = string;
275 s = strchr(remainder, ';');
276 while (s && s > remainder && s[-1] == '\\')
277 s = strchr(s + 1, ';');
278
279 while (s) {
280 gsize len;
281
282 len = s - remainder;
283 /* skip empty fields: */
284 if (len > 0) {
285 string_list =
286 g_slist_prepend(string_list, g_strndup(remainder, len));
287 n++;
288 }
289 remainder = s + 1;
290 s = strchr(remainder, ';');
291 while (s && s > remainder && s[-1] == '\\')
292 s = strchr(s + 1, ';');
293 }
294
295 if (*string) {
296 n++;
297 string_list = g_slist_prepend(string_list, g_strdup(remainder));
298 }
299
300 str_array = g_new(gchar*, n + 1);
301
302 str_array[n--] = NULL;
303 for (slist = string_list; slist; slist = slist->next) {
304 gchar * str = (gchar *) slist->data;
305 gchar * p;
306
307 while ((p = strstr(str, "\\;"))) {
308 gchar * newstr = g_malloc(strlen(str));
309
310 strncpy(newstr, str, p - str);
311 strcpy(newstr + (p - str), p + 1);
312 g_free(str);
313 str = newstr;
314 }
315 str_array[n--] = str;
316 }
317
318 g_slist_free(string_list);
319
320 return str_array;
321 }
322
323 /*
324 * Create a LibBalsaAddress from a vCard; return NULL if the string does
325 * not contain a complete vCard with at least one email address.
326 */
327
328 LibBalsaAddress*
libbalsa_address_new_from_vcard(const gchar * str,const gchar * charset)329 libbalsa_address_new_from_vcard(const gchar *str, const gchar *charset)
330 {
331 gchar *name = NULL, *nick_name = NULL, *org = NULL;
332 gchar *full_name = NULL, *last_name = NULL, *first_name = NULL;
333 gint in_vcard = FALSE;
334 GList *address_list = NULL;
335 const gchar *string, *next_line;
336 gchar * vcard;
337
338 g_return_val_if_fail(str, NULL);
339
340 /* rfc 2425 unfold the string */
341 vcard = g_strdup(str);
342 while ((string = strstr(vcard, "\r\n ")) ||
343 (string = strstr(vcard, "\r\n\t"))) {
344 gchar * newstr = g_malloc0(strlen(vcard) - 2);
345
346 strncpy(newstr, vcard, string - vcard);
347 strcpy(newstr + (string - vcard), string + 3);
348 g_free(vcard);
349 vcard = newstr;
350 }
351 while ((string = strstr(vcard, "\n ")) ||
352 (string = strstr(vcard, "\n\t"))) {
353 gchar * newstr = g_malloc(strlen(vcard) - 1);
354
355 strncpy(newstr, vcard, string - vcard);
356 strcpy(newstr + (string - vcard), string + 2);
357 g_free(vcard);
358 vcard = newstr;
359 }
360
361 /* may contain \r's when decoded from base64... */
362 while ((string = strstr(vcard, "\r\n ")) ||
363 (string = strstr(vcard, "\r\n\t"))) {
364 gchar * newstr = g_malloc(strlen(vcard) - 2);
365
366 strncpy(newstr, vcard, string - vcard);
367 strcpy(newstr + (string - vcard), string + 3);
368 g_free(vcard);
369 vcard = newstr;
370 }
371
372 /* process */
373 for(string = vcard; *string; string = next_line) {
374 gchar *line;
375
376 next_line = strchr(string, '\n');
377 if (next_line)
378 ++next_line;
379 else
380 next_line = string + strlen(string);
381 /*
382 * Check if it is a card.
383 */
384 if (g_ascii_strncasecmp(string, "BEGIN:VCARD", 11) == 0)
385 in_vcard = TRUE;
386 else if (in_vcard) {
387 if (g_ascii_strncasecmp(string, "END:VCARD", 9) == 0) {
388 /*
389 * We are done loading a card.
390 */
391 LibBalsaAddress *address;
392
393 if (!address_list)
394 break;
395
396 address = g_object_new(LIBBALSA_TYPE_ADDRESS, NULL);
397
398 if (full_name) {
399 address->full_name = full_name;
400 g_free(name);
401 } else if (name)
402 address->full_name = name;
403 else if (nick_name)
404 address->full_name = g_strdup(nick_name);
405 else
406 address->full_name = g_strdup(_("No-Name"));
407
408 address->last_name = last_name;
409 address->first_name = first_name;
410 address->nick_name = nick_name;
411 address->organization = org;
412 address->address_list = g_list_reverse(address_list);
413
414 return address;
415 }
416
417 line = g_strndup(string, next_line - string);
418 g_strchomp(line);
419
420 /* Encoding of national characters:
421 * - vcard 2.1 allows charset=xxx and encoding=(base64|quoted-printable|8bit)
422 * - Thunderbird claims to use vcard 2.1, but uses only "quoted-printable",
423 * and the charset from part MIME header
424 * - vcard 3.0 (rfc 2426) allows "encoding=b", the charset must be taken
425 * from the content-type MIME header */
426 if (strchr(line, ':')) {
427 gchar ** parts = g_strsplit(line, ":", 2);
428 gchar ** tokens;
429 gint n;
430
431 /* split control stuff into tokens */
432 tokens = g_strsplit(parts[0], ";", -1);
433
434 /* find encoding= */
435 for (n = 0; tokens[n]; n++)
436 if (!g_ascii_strncasecmp(tokens[n], "encoding=", 9)) {
437 if (!g_ascii_strcasecmp(tokens[n] + 9, "base64")) {
438 /* vcard 2.1: use the charset parameter (below) */
439 parts[1] = vcard_b64_decode(parts[1]);
440 } else if (!g_ascii_strcasecmp(tokens[n] + 9, "b")) {
441 /* rfc 2426: charset from MIME part */
442 parts[1] = vcard_b64_decode(parts[1]);
443 parts[1] = vcard_charset_to_utf8(parts[1], charset);
444 } else if (!g_ascii_strcasecmp(tokens[n] + 9, "quoted-printable")) {
445 /* vcard 2.1: use the charset parameter (below) */
446 parts[1] = vcard_qp_decode(parts[1]);
447 }
448 }
449
450 /* find quoted-printable */
451 for (n = 0; tokens[n]; n++)
452 if (!g_ascii_strcasecmp(tokens[n], "quoted-printable")) {
453 /* Thunderbird: broken vcard 2.1, charset from MIME part */
454 parts[1] = vcard_qp_decode(parts[1]);
455 parts[1] = vcard_charset_to_utf8(parts[1], charset);
456 }
457
458 /* find charset= (vcard 2.1 only) */
459 for (n = 0; tokens[n]; n++)
460 if (!g_ascii_strncasecmp(tokens[n], "charset=", 8))
461 parts[1] = vcard_charset_to_utf8(parts[1], tokens[n] + 8);
462
463 /* construct the result */
464 g_free(line);
465 line = g_strdup_printf("%s:%s", tokens[0], parts[1]);
466
467 /* clean up */
468 g_strfreev(tokens);
469 g_strfreev(parts);
470 }
471
472 if (g_ascii_strncasecmp(line, "FN:", 3) == 0) {
473
474 g_free(full_name);
475 full_name = g_strdup(line + 3);
476 full_name = validate_vcard_string(full_name, charset);
477
478 } else if (g_ascii_strncasecmp(line, "N:", 2) == 0) {
479
480 g_free(name);
481 g_free(last_name);
482 g_free(first_name);
483 name = libbalsa_address_extract_name(line + 2,
484 &last_name, &first_name);
485 name = validate_vcard_string(name, charset);
486 last_name = validate_vcard_string(last_name, charset);
487 first_name = validate_vcard_string(first_name, charset);
488
489 } else if (g_ascii_strncasecmp(line, "NICKNAME:", 9) == 0) {
490
491 g_free(nick_name);
492 nick_name = g_strdup(line + 9);
493 nick_name = validate_vcard_string(nick_name, charset);
494
495 } else if (g_ascii_strncasecmp(line, "ORG:", 4) == 0) {
496 gchar ** org_strs = vcard_strsplit(line + 4);
497
498 g_free(org);
499 org = g_strjoinv(", ", org_strs);
500 g_strfreev(org_strs);
501 org = validate_vcard_string(org, charset);
502
503 } else if (g_ascii_strncasecmp(line, "EMAIL:", 6) == 0) {
504
505 address_list =
506 g_list_prepend(address_list, g_strdup(line + 6));
507 }
508 g_free(line);
509 }
510 }
511
512 g_free(full_name);
513 g_free(name);
514 g_free(last_name);
515 g_free(first_name);
516 g_free(nick_name);
517 g_free(org);
518 g_list_foreach(address_list, (GFunc) g_free, NULL);
519 g_list_free(address_list);
520
521 return NULL;
522 }
523
524 void
libbalsa_address_set_copy(LibBalsaAddress * dest,LibBalsaAddress * src)525 libbalsa_address_set_copy(LibBalsaAddress * dest, LibBalsaAddress * src)
526 {
527 GList *src_al, *dst_al;
528
529 if (dest == src) /* safety check */
530 return;
531
532 g_free(dest->nick_name);
533 dest->nick_name = g_strdup(src->nick_name);
534 g_free(dest->full_name);
535 dest->full_name = g_strdup(src->full_name);
536 g_free(dest->first_name);
537 dest->first_name = g_strdup(src->first_name);
538 g_free(dest->last_name);
539 dest->last_name = g_strdup(src->last_name);
540 g_free(dest->organization);
541 dest->organization = g_strdup(src->organization);
542 g_list_foreach(dest->address_list, (GFunc) g_free, NULL);
543 g_list_free(dest->address_list);
544
545 dst_al = NULL;
546 for (src_al = src->address_list; src_al; src_al = src_al->next)
547 dst_al = g_list_prepend(dst_al, g_strdup(src_al->data));
548 dest->address_list = g_list_reverse(dst_al);
549 }
550
551 static gchar *
rfc2822_mailbox(const gchar * full_name,const gchar * address)552 rfc2822_mailbox(const gchar * full_name, const gchar * address)
553 {
554 InternetAddress *ia;
555 gchar *new_str;
556
557 ia = internet_address_mailbox_new(full_name, address);
558 new_str = internet_address_to_string(ia, FALSE);
559 g_object_unref(ia);
560
561 return new_str;
562 }
563
564 static gchar*
rfc2822_group(const gchar * full_name,GList * addr_list)565 rfc2822_group(const gchar *full_name, GList *addr_list)
566 {
567 InternetAddress *ia;
568 gchar *res;
569
570 ia = internet_address_group_new(full_name);
571 for (; addr_list; addr_list = addr_list->next) {
572 InternetAddress *member;
573
574 member = internet_address_mailbox_new(NULL, addr_list->data);
575 internet_address_group_add_member(INTERNET_ADDRESS_GROUP(ia), member);
576 g_object_unref(member);
577 }
578 res = internet_address_to_string(ia, FALSE);
579 g_object_unref(ia);
580
581 return res;
582 }
583
584 /*
585 Get a string version of this address.
586
587 If n == -1 then return all addresses, else return the n'th one.
588 If n > the number of addresses, will cause an error.
589 */
590 gchar *
libbalsa_address_to_gchar(LibBalsaAddress * address,gint n)591 libbalsa_address_to_gchar(LibBalsaAddress * address, gint n)
592 {
593 gchar *retc = NULL;
594
595 g_return_val_if_fail(LIBBALSA_IS_ADDRESS(address), NULL);
596
597 if(!address->address_list)
598 return NULL;
599 if(n==-1) {
600 if(address->address_list->next)
601 retc = rfc2822_group(address->full_name, address->address_list);
602 else
603 retc = rfc2822_mailbox(address->full_name,
604 address->address_list->data);
605 } else {
606 const gchar *mailbox = g_list_nth_data(address->address_list, n);
607 g_return_val_if_fail(mailbox != NULL, NULL);
608
609 retc = rfc2822_mailbox(address->full_name, mailbox);
610 }
611
612 return retc;
613 }
614
615 /* Helper */
616 static const gchar *
lba_get_name_or_mailbox(InternetAddressList * address_list,gboolean get_name,gboolean in_group)617 lba_get_name_or_mailbox(InternetAddressList * address_list,
618 gboolean get_name, gboolean in_group)
619 {
620 const gchar *retval = NULL;
621 InternetAddress *ia;
622 gint i, len;
623
624 if (address_list == NULL)
625 return NULL;
626
627 len = internet_address_list_length(address_list);
628 for (i = 0; i < len; i++) {
629 ia = internet_address_list_get_address (address_list, i);
630
631 if (get_name && ia->name && *ia->name)
632 return ia->name;
633
634 if (INTERNET_ADDRESS_IS_MAILBOX (ia))
635 retval = INTERNET_ADDRESS_MAILBOX (ia)->addr;
636 else {
637 if (in_group)
638 g_message("Ignoring nested group address");
639 else
640 retval = lba_get_name_or_mailbox(INTERNET_ADDRESS_GROUP(ia)->members,
641 get_name, TRUE);
642 }
643 if (retval)
644 break;
645 }
646
647 return retval;
648 }
649
650 /* Get either a name or a mailbox from an InternetAddressList. */
651 const gchar *
libbalsa_address_get_name_from_list(InternetAddressList * address_list)652 libbalsa_address_get_name_from_list(InternetAddressList *address_list)
653 {
654 return lba_get_name_or_mailbox(address_list, TRUE, FALSE);
655 }
656
657 /* Get a mailbox from an InternetAddressList. */
658 const gchar *
libbalsa_address_get_mailbox_from_list(InternetAddressList * address_list)659 libbalsa_address_get_mailbox_from_list(InternetAddressList *address_list)
660 {
661 return lba_get_name_or_mailbox(address_list, FALSE, FALSE);
662 }
663
664 /* Number of individual mailboxes in an InternetAddressList. */
665 gint
libbalsa_address_n_mailboxes_in_list(InternetAddressList * address_list)666 libbalsa_address_n_mailboxes_in_list(InternetAddressList * address_list)
667 {
668 gint i, len, n_mailboxes = 0;
669
670 g_return_val_if_fail(IS_INTERNET_ADDRESS_LIST(address_list), -1);
671
672 len = internet_address_list_length(address_list);
673 for (i = 0; i < len; i++) {
674 const InternetAddress *ia =
675 internet_address_list_get_address(address_list, i);
676
677 if (INTERNET_ADDRESS_IS_MAILBOX(ia))
678 ++n_mailboxes;
679 else
680 n_mailboxes +=
681 libbalsa_address_n_mailboxes_in_list(INTERNET_ADDRESS_GROUP
682 (ia)->members);
683 }
684
685 return n_mailboxes;
686 }
687
688 /* =================================================================== */
689 /* UI PART */
690 /* =================================================================== */
691
692 /** libbalsa_address_set_edit_entries() initializes the GtkEntry widgets
693 in entries with values from address
694 */
695 void
libbalsa_address_set_edit_entries(const LibBalsaAddress * address,GtkWidget ** entries)696 libbalsa_address_set_edit_entries(const LibBalsaAddress * address,
697 GtkWidget **entries)
698 {
699 gchar *new_name = NULL;
700 gchar *new_email = NULL;
701 gchar *new_organization = NULL;
702 gchar *first_name = NULL;
703 gchar *last_name = NULL;
704 gchar *nick_name = NULL;
705 gint cnt;
706 GtkListStore *store;
707 GtkTreeIter iter;
708
709 new_email = g_strdup(address
710 && address->address_list
711 && address->address_list->data ?
712 address->address_list->data : "");
713 /* initialize the organization... */
714 if (!address || address->organization == NULL)
715 new_organization = g_strdup("");
716 else
717 new_organization = g_strdup(address->organization);
718
719 /* if the message only contains an e-mail address */
720 if (!address || address->full_name == NULL)
721 new_name = g_strdup(new_email);
722 else {
723 gchar **names;
724 g_assert(address);
725 /* make sure address->personal is not all whitespace */
726 new_name = g_strstrip(g_strdup(address->full_name));
727
728 /* guess the first name and last name */
729 if (*new_name != '\0') {
730 names = g_strsplit(new_name, " ", 0);
731
732 for (cnt=0; names[cnt]; cnt++)
733 ;
734
735 /* get first name */
736 first_name = g_strdup(address->first_name
737 ? address->first_name : names[0]);
738
739 /* get last name */
740 if(address->last_name)
741 last_name = g_strdup(address->last_name);
742 else {
743 if (cnt == 1)
744 last_name = g_strdup("");
745 else
746 last_name = g_strdup(names[cnt - 1]);
747 }
748 g_strfreev(names);
749 }
750 }
751
752 if (first_name == NULL)
753 first_name = g_strdup("");
754 if (last_name == NULL)
755 last_name = g_strdup("");
756 if (!address || address->nick_name == NULL)
757 nick_name = g_strdup("");
758 else
759 nick_name = g_strdup(address->nick_name);
760
761 /* Full name must be set after first and last names. */
762 gtk_entry_set_text(GTK_ENTRY(entries[FIRST_NAME]), first_name);
763 gtk_entry_set_text(GTK_ENTRY(entries[LAST_NAME]), last_name);
764 gtk_entry_set_text(GTK_ENTRY(entries[FULL_NAME]), new_name);
765 gtk_entry_set_text(GTK_ENTRY(entries[NICK_NAME]), nick_name);
766 gtk_entry_set_text(GTK_ENTRY(entries[ORGANIZATION]), new_organization);
767
768 store = GTK_LIST_STORE(gtk_tree_view_get_model
769 (GTK_TREE_VIEW(entries[EMAIL_ADDRESS])));
770 gtk_list_store_clear(store);
771 if (address) {
772 GList *list;
773
774 for (list = address->address_list; list; list = list->next) {
775 gtk_list_store_append(store, &iter);
776 gtk_list_store_set(store, &iter, 0, list->data, -1);
777 }
778 } else {
779 gtk_list_store_append(store, &iter);
780 gtk_list_store_set(store, &iter, 0, "", -1);
781 }
782
783 gtk_editable_select_region(GTK_EDITABLE(entries[FULL_NAME]), 0, -1);
784
785 for (cnt = FULL_NAME + 1; cnt < NUM_FIELDS; cnt++)
786 if (GTK_IS_EDITABLE(entries[cnt]))
787 gtk_editable_set_position(GTK_EDITABLE(entries[cnt]), 0);
788
789 g_free(new_name);
790 g_free(first_name);
791 g_free(last_name);
792 g_free(nick_name);
793 g_free(new_email);
794 g_free(new_organization);
795 gtk_widget_grab_focus(entries[FULL_NAME]);
796 }
797
798 /** libbalsa_address_get_edit_widget() returns an widget adapted
799 for a LibBalsaAddress edition, with initial values set if address
800 is provided. The edit entries are set in entries array
801 and enumerated with LibBalsaAddressField constants
802 */
803 static void
lba_entry_changed(GtkEntry * entry,GtkEntry ** entries)804 lba_entry_changed(GtkEntry * entry, GtkEntry ** entries)
805 {
806 gchar *full_name =
807 g_strconcat(gtk_entry_get_text(entries[FIRST_NAME]), " ",
808 gtk_entry_get_text(entries[LAST_NAME]), NULL);
809 gtk_entry_set_text(entries[FULL_NAME], full_name);
810 g_free(full_name);
811 }
812
813 static void
lba_cell_edited(GtkCellRendererText * cell,const gchar * path_string,const gchar * new_text,GtkListStore * store)814 lba_cell_edited(GtkCellRendererText * cell, const gchar * path_string,
815 const gchar * new_text, GtkListStore * store)
816 {
817 GtkTreeIter iter;
818
819 if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store),
820 &iter, path_string))
821 gtk_list_store_set(store, &iter, 0, new_text, -1);
822 }
823
824 static GtkWidget *
lba_address_list_widget(GCallback changed_cb,gpointer changed_data)825 lba_address_list_widget(GCallback changed_cb, gpointer changed_data)
826 {
827 GtkListStore *store;
828 GtkWidget *tree_view;
829 GtkCellRenderer *renderer;
830 GtkTreeViewColumn *column;
831
832 store = gtk_list_store_new(1, G_TYPE_STRING);
833 tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
834 g_object_unref(store);
835 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), FALSE);
836
837 renderer = gtk_cell_renderer_text_new();
838 g_object_set(renderer, "editable", TRUE, NULL);
839 g_signal_connect(renderer, "edited", G_CALLBACK(lba_cell_edited),
840 store);
841 if (changed_cb)
842 g_signal_connect_swapped(renderer, "edited",
843 changed_cb, changed_data);
844
845 column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
846 "text", 0, NULL);
847 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
848
849 return tree_view;
850 }
851
852 static void
add_row(GtkWidget * button,gpointer data)853 add_row(GtkWidget*button, gpointer data)
854 {
855 GtkTreeView *tv = GTK_TREE_VIEW(data);
856 GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tv));
857 GtkTreeIter iter;
858 GtkTreePath *path;
859 gtk_list_store_insert_with_values(store, &iter, 99999, 0, "", -1);
860 gtk_widget_grab_focus(GTK_WIDGET(tv));
861 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
862 gtk_tree_view_set_cursor(tv, path, NULL, TRUE);
863 gtk_tree_path_free(path);
864 }
865
866 GtkTargetEntry libbalsa_address_target_list[2] = {
867 {"text/plain", 0, LIBBALSA_ADDRESS_TRG_STRING },
868 {"x-application/x-addr", GTK_TARGET_SAME_APP,LIBBALSA_ADDRESS_TRG_ADDRESS}
869 };
870
871 static void
addrlist_drag_received_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint target_type,guint32 time,gpointer data)872 addrlist_drag_received_cb(GtkWidget * widget, GdkDragContext * context,
873 gint x, gint y, GtkSelectionData * selection_data,
874 guint target_type, guint32 time, gpointer data)
875 {
876 GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
877 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
878 GtkTreeIter iter;
879 gboolean dnd_success = FALSE;
880 LibBalsaAddress *addr;
881
882 printf("drag_received:\n");
883 /* Deal with what we are given from source */
884 if(selection_data
885 && gtk_selection_data_get_length(selection_data) >= 0) {
886 switch (target_type) {
887 case LIBBALSA_ADDRESS_TRG_ADDRESS:
888 addr = *(LibBalsaAddress **)
889 gtk_selection_data_get_data(selection_data);
890 if(addr && addr->address_list) {
891 g_print ("string: %s\n", (gchar*)addr->address_list->data);
892 gtk_list_store_insert_with_values(GTK_LIST_STORE(model),
893 &iter, 99999,
894 0,
895 addr->address_list->data,
896 -1);
897 dnd_success = TRUE;
898 }
899 break;
900 case LIBBALSA_ADDRESS_TRG_STRING:
901 g_print("text/plain target not implemented.\n");
902 break;
903 default: g_print ("nothing good");
904 }
905 }
906
907 if (!dnd_success)
908 g_print ("DnD data transfer failed!\n");
909
910 gtk_drag_finish(context, dnd_success, FALSE, time);
911 }
912
913 static gboolean
addrlist_drag_drop_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer user_data)914 addrlist_drag_drop_cb(GtkWidget *widget, GdkDragContext *context,
915 gint x, gint y, guint time, gpointer user_data)
916 {
917 gboolean is_valid_drop_site;
918 GdkAtom target_type;
919 GList *targets;
920
921 /* Check to see if (x,y) is a valid drop site within widget */
922 is_valid_drop_site = TRUE;
923
924 /* If the source offers a target */
925 targets = gdk_drag_context_list_targets(context);
926 if (targets) {
927 /* Choose the best target type */
928 target_type = GDK_POINTER_TO_ATOM
929 (g_list_nth_data (targets, LIBBALSA_ADDRESS_TRG_ADDRESS));
930
931 /* Request the data from the source. */
932 printf("drag_drop requests target=%p\n", target_type);
933 gtk_drag_get_data
934 (
935 widget, /* will receive 'drag-data-received' signal */
936 context, /* represents the current state of the DnD */
937 target_type, /* the target type we want */
938 time /* time stamp */
939 );
940 } else {
941 is_valid_drop_site = FALSE;
942 }
943
944 return is_valid_drop_site;
945 }
946
947
948 GtkWidget*
libbalsa_address_get_edit_widget(const LibBalsaAddress * address,GtkWidget ** entries,GCallback changed_cb,gpointer changed_data)949 libbalsa_address_get_edit_widget(const LibBalsaAddress *address,
950 GtkWidget **entries,
951 GCallback changed_cb, gpointer changed_data)
952 {
953 const static gchar *labels[NUM_FIELDS] = {
954 N_("D_isplayed Name:"),
955 N_("_First Name:"),
956 N_("_Last Name:"),
957 N_("_Nickname:"),
958 N_("O_rganization:"),
959 N_("_Email Address:")
960 };
961
962 GtkWidget *grid, *label, *lhs;
963 gint cnt;
964
965 grid = gtk_grid_new();
966 #define HIG_PADDING 6
967 gtk_grid_set_row_spacing(GTK_GRID(grid), HIG_PADDING);
968 gtk_grid_set_column_spacing(GTK_GRID(grid), HIG_PADDING);
969 gtk_container_set_border_width(GTK_CONTAINER(grid), HIG_PADDING);
970
971 for (cnt = 0; cnt < NUM_FIELDS; cnt++) {
972 if (!labels[cnt])
973 continue;
974 label = gtk_label_new_with_mnemonic(_(labels[cnt]));
975 gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.0);
976 if (cnt == EMAIL_ADDRESS) {
977 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
978 GtkWidget *but = gtk_button_new_with_mnemonic(_("A_dd"));
979 entries[cnt] = lba_address_list_widget(changed_cb,
980 changed_data);
981 gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 1);
982 gtk_box_pack_start(GTK_BOX(box), but, FALSE, FALSE, 1);
983 lhs = box;
984 g_signal_connect(but, "clicked", G_CALLBACK(add_row),
985 entries[cnt]);
986 gtk_drag_dest_set(entries[cnt],
987 GTK_DEST_DEFAULT_MOTION |
988 GTK_DEST_DEFAULT_HIGHLIGHT,
989 libbalsa_address_target_list,
990 2, /* size of list */
991 GDK_ACTION_COPY);
992 g_signal_connect(G_OBJECT(entries[cnt]), "drag-data-received",
993 G_CALLBACK(addrlist_drag_received_cb), NULL);
994 g_signal_connect (G_OBJECT(entries[cnt]), "drag-drop",
995 G_CALLBACK (addrlist_drag_drop_cb), NULL);
996 } else {
997 entries[cnt] = gtk_entry_new();
998 if (changed_cb)
999 g_signal_connect_swapped(entries[cnt], "changed",
1000 changed_cb, changed_data);
1001 lhs = label;
1002 }
1003 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entries[cnt]);
1004
1005 gtk_grid_attach(GTK_GRID(grid), lhs, 0, cnt + 1, 1, 1);
1006
1007 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1008
1009 gtk_widget_set_hexpand(entries[cnt], TRUE);
1010 gtk_widget_set_vexpand(entries[cnt], TRUE);
1011 gtk_grid_attach(GTK_GRID(grid), entries[cnt], 1, cnt + 1, 1, 1);
1012 }
1013 g_signal_connect(entries[FIRST_NAME], "changed",
1014 G_CALLBACK(lba_entry_changed), entries);
1015 g_signal_connect(entries[LAST_NAME], "changed",
1016 G_CALLBACK(lba_entry_changed), entries);
1017
1018 libbalsa_address_set_edit_entries(address, entries);
1019
1020 if (changed_cb) {
1021 GtkTreeModel *model =
1022 gtk_tree_view_get_model(GTK_TREE_VIEW(entries[EMAIL_ADDRESS]));
1023 g_signal_connect_swapped(G_OBJECT(model), "row-inserted",
1024 changed_cb, changed_data);
1025 g_signal_connect_swapped(G_OBJECT(model), "row-changed",
1026 changed_cb, changed_data);
1027 g_signal_connect_swapped(G_OBJECT(model), "row-deleted",
1028 changed_cb, changed_data);
1029 }
1030 return grid;
1031 }
1032
1033 LibBalsaAddress *
libbalsa_address_new_from_edit_entries(GtkWidget ** entries)1034 libbalsa_address_new_from_edit_entries(GtkWidget ** entries)
1035 {
1036 #define SET_FIELD(f,e)\
1037 do{ (f) = g_strstrip(gtk_editable_get_chars(GTK_EDITABLE(e), 0, -1));\
1038 if( !(f) || !*(f)) { g_free(f); (f) = NULL; } \
1039 else { while( (p=strchr(address->full_name,';'))) *p = ','; } } while(0)
1040
1041 LibBalsaAddress *address;
1042 char *p;
1043 GList *list = NULL;
1044 GtkTreeModel *model;
1045 gboolean valid;
1046 GtkTreeIter iter;
1047
1048 /* make sure gtk_tree_model looses focus, otherwise the list does
1049 * not get updated (gtk2-2.8.20) */
1050 gtk_widget_grab_focus(entries[FULL_NAME]);
1051 /* FIXME: This problem should be solved in the VCard
1052 implementation in libbalsa: semicolons mess up how GnomeCard
1053 processes the fields, so disallow them and replace them
1054 by commas. */
1055
1056 address = libbalsa_address_new();
1057 SET_FIELD(address->full_name, entries[FULL_NAME]);
1058 if (!address->full_name) {
1059 g_object_unref(address);
1060 return NULL;
1061 }
1062 SET_FIELD(address->first_name, entries[FIRST_NAME]);
1063 SET_FIELD(address->last_name, entries[LAST_NAME]);
1064 SET_FIELD(address->nick_name, entries[NICK_NAME]);
1065 SET_FIELD(address->organization,entries[ORGANIZATION]);
1066
1067 model = gtk_tree_view_get_model(GTK_TREE_VIEW(entries[EMAIL_ADDRESS]));
1068 for (valid = gtk_tree_model_get_iter_first(model, &iter); valid;
1069 valid = gtk_tree_model_iter_next(model, &iter)) {
1070 gchar *email;
1071
1072 gtk_tree_model_get(model, &iter, 0, &email, -1);
1073 if (email && *email)
1074 list = g_list_prepend(list, email);
1075 }
1076 address->address_list = g_list_reverse(list);
1077
1078 return address;
1079 }
1080