2f28c91ef87dc23b08a38f46a245802ff8021fab
[modest] / src / modest-text-utils.c
1 /* Copyright (c) 2006, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30
31 /* modest-ui.c */
32
33 #include <gtk/gtk.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <glib/gi18n.h>
37 #include "modest-text-utils.h"
38
39
40 #ifdef HAVE_CONFIG_H
41 #include <config.h>
42 #endif /*HAVE_CONFIG_H */
43
44 /* private */
45 static GString *get_next_line (const char *b, const gsize blen, const gchar * iter);
46 static int get_indent_level (const char *l);
47 static void unquote_line (GString * l);
48 static void append_quoted (GString * buf, const int indent,
49                            const GString * str, const int cutpoint);
50 static int get_breakpoint_utf8 (const gchar * s, const gint indent,
51                                 const gint limit);
52 static int get_breakpoint_ascii (const gchar * s, const gint indent,
53                                  const gint limit);
54 static int get_breakpoint (const gchar * s, const gint indent,
55                            const gint limit);
56
57 static GString *
58 get_next_line (const gchar * b, const gsize blen, const gchar * iter)
59 {
60         GString *gs;
61         const gchar *i0;
62         
63         if (iter > b + blen)
64                 return g_string_new("");
65         
66         i0 = iter;
67         while (iter[0]) {
68                 if (iter[0] == '\n')
69                         break;
70                 iter++;
71         }
72         gs = g_string_new_len (i0, iter - i0);
73         return gs;
74 }
75 static int
76 get_indent_level (const char *l)
77 {
78         int indent = 0;
79
80         while (l[0]) {
81                 if (l[0] == '>') {
82                         indent++;
83                         if (l[1] == ' ') {
84                                 l++;
85                         }
86                 } else {
87                         break;
88                 }
89                 l++;
90
91         }
92
93         /*      if we hit the signature marker "-- ", we return -(indent + 1). This
94          *      stops reformatting.
95          */
96         if (strcmp (l, "-- ") == 0) {
97                 return -1 - indent;
98         } else {
99                 return indent;
100         }
101 }
102
103 static void
104 unquote_line (GString * l)
105 {
106         gchar *p;
107
108         p = l->str;
109         while (p[0]) {
110                 if (p[0] == '>') {
111                         if (p[1] == ' ') {
112                                 p++;
113                         }
114                 } else {
115                         break;
116                 }
117                 p++;
118         }
119         g_string_erase (l, 0, p - l->str);
120 }
121
122 static void
123 append_quoted (GString * buf, int indent, const GString * str,
124                const int cutpoint)
125 {
126         int i;
127
128         indent = indent < 0 ? abs (indent) - 1 : indent;
129         for (i = 0; i <= indent; i++) {
130                 g_string_append (buf, "> ");
131         }
132         if (cutpoint > 0) {
133                 g_string_append_len (buf, str->str, cutpoint);
134         } else {
135                 g_string_append (buf, str->str);
136         }
137         g_string_append (buf, "\n");
138 }
139
140 static int
141 get_breakpoint_utf8 (const gchar * s, gint indent, const gint limit)
142 {
143         gint index = 0;
144         const gchar *pos, *last;
145         gunichar *uni;
146
147         indent = indent < 0 ? abs (indent) - 1 : indent;
148
149         last = NULL;
150         pos = s;
151         uni = g_utf8_to_ucs4_fast (s, -1, NULL);
152         while (pos[0]) {
153                 if ((index + 2 * indent > limit) && last) {
154                         g_free (uni);
155                         return last - s;
156                 }
157                 if (g_unichar_isspace (uni[index])) {
158                         last = pos;
159                 }
160                 pos = g_utf8_next_char (pos);
161                 index++;
162         }
163         g_free (uni);
164         return strlen (s);
165 }
166
167 static int
168 get_breakpoint_ascii (const gchar * s, const gint indent, const gint limit)
169 {
170         gint i, last;
171
172         last = strlen (s);
173         if (last + 2 * indent < limit)
174                 return last;
175
176         for (i = strlen (s); i > 0; i--) {
177                 if (s[i] == ' ') {
178                         if (i + 2 * indent <= limit) {
179                                 return i;
180                         } else {
181                                 last = i;
182                         }
183                 }
184         }
185         return last;
186 }
187
188 static int
189 get_breakpoint (const gchar * s, const gint indent, const gint limit)
190 {
191
192         if (g_utf8_validate (s, -1, NULL)) {
193                 return get_breakpoint_utf8 (s, indent, limit);
194         } else {                /* assume ASCII */
195                 //g_warning("invalid UTF-8 in msg");
196                 return get_breakpoint_ascii (s, indent, limit);
197         }
198 }
199
200
201
202 /* just to prevent warnings:
203  * warning: `%x' yields only last 2 digits of year in some locales
204  */
205 static size_t
206 my_strftime(char *s, size_t max, const char  *fmt,  const
207             struct tm *tm) {
208         return strftime(s, max, fmt, tm);
209 }
210
211 static gchar *
212 cite (const time_t sent_date, const gchar *from) {
213         gchar *str;
214         gchar sent_str[101];
215
216         /* format sent_date */
217         my_strftime (sent_str, 100, "%c", localtime (&sent_date));
218         return g_strdup_printf (N_("On %s, %s wrote:\n"), sent_str, from);
219 }
220
221
222 gchar *
223 modest_text_utils_quote (const gchar * to_quote, const gchar * from,
224                          const time_t sent_date, const int limit)
225 {
226         const gchar *iter;
227         gint indent, breakpoint, rem_indent = 0;
228         GString *q, *l, *remaining;
229         gsize len;
230         gchar *tmp;
231
232         /* format sent_date */
233         tmp = cite (sent_date, from);
234         q = g_string_new (tmp);
235         g_free (tmp);
236
237         /* remaining will store the rest of the line if we have to break it */
238         remaining = g_string_new ("");
239
240         iter = to_quote;
241         len = strlen(to_quote);
242         do {
243                 l = get_next_line (to_quote, len, iter);
244                 iter = iter + l->len + 1;
245                 indent = get_indent_level (l->str);
246                 unquote_line (l);
247
248                 if (remaining->len) {
249                         if (l->len && indent == rem_indent) {
250                                 g_string_prepend (l, " ");
251                                 g_string_prepend (l, remaining->str);
252                         } else {
253                                 do {
254                                         breakpoint =
255                                                 get_breakpoint (remaining->     str,
256                                                                 rem_indent,
257                                                                 limit);
258                                         append_quoted (q, rem_indent,
259                                                        remaining, breakpoint);
260                                         g_string_erase (remaining, 0,
261                                                         breakpoint);
262                                         if (remaining->str[0] == ' ') {
263                                                 g_string_erase (remaining, 0,
264                                                                 1);
265                                         }
266                                 } while (remaining->len);
267                         }
268                 }
269                 g_string_free (remaining, TRUE);
270                 breakpoint = get_breakpoint (l->str, indent, limit);
271                 remaining = g_string_new (l->str + breakpoint);
272                 if (remaining->str[0] == ' ') {
273                         g_string_erase (remaining, 0, 1);
274                 }
275                 rem_indent = indent;
276                 append_quoted (q, indent, l, breakpoint);
277                 g_string_free (l, TRUE);
278         } while ((iter < to_quote + len) || (remaining->str[0]));
279
280         return g_string_free (q, FALSE);
281 }
282
283 static gchar *
284 create_derivated_subject (const gchar *subject, const gchar *prefix)
285 {
286         gchar *tmp, *buffer;
287
288         if (!subject)
289                 return g_strdup_printf ("%s ", prefix);
290
291         tmp = g_strchug (g_strdup (subject));
292
293         if (!strncmp (tmp, prefix, strlen (prefix))) {
294                 return tmp;
295         } else {
296                 g_free (tmp);
297                 return g_strdup_printf ("%s %s", prefix, subject);
298         }
299 }
300
301 /**
302  * modest_text_utils_create_reply_subject:
303  * @subject: 
304  * 
305  * creates a new subject with a reply prefix if not present before
306  * 
307  * Returns: a new subject with the reply prefix
308  **/
309 gchar * 
310 modest_text_utils_create_reply_subject (const gchar *subject)
311 {
312         return create_derivated_subject (subject, _("Re:"));
313 }
314
315 /**
316  * modest_text_utils_create_forward_subject:
317  * @subject: 
318  * 
319  * creates a new subject with a forward prefix if not present before
320  * 
321  * Returns:  a new subject with the forward prefix
322  **/
323 gchar * 
324 modest_text_utils_create_forward_subject (const gchar *subject)
325 {
326         return create_derivated_subject (subject, _("Fw:"));
327 }
328
329 gchar *
330 modest_text_utils_create_cited_text (const gchar *from, 
331                                      time_t sent_date, 
332                                      const gchar *text)
333 {
334         gchar *tmp, *retval;
335
336         tmp = cite (sent_date, from);
337         retval = g_strdup_printf ("%s%s\n", tmp, text);
338         g_free (tmp);
339
340         return retval;
341 }
342
343 /**
344  * modest_text_utils_create_inlined_text:
345  * @text: the original text
346  * 
347  * creates a new string with the "Original message" text prepended to
348  * the text passed as argument and some data of the header
349  * 
350  * Returns:  a newly allocated text
351  **/
352 gchar * 
353 modest_text_utils_create_inlined_text (const gchar *from,
354                                        time_t sent_date,
355                                        const gchar *to,
356                                       const gchar *subject,
357                                       const gchar *text)
358 {
359         gchar sent_str[101];
360
361         my_strftime (sent_str, 100, "%c", localtime (&sent_date));
362
363         return g_strdup_printf ("%s\n%s %s\n%s %s\n%s %s\n%s %s\n\n%s", 
364                                 _("-----Forwarded Message-----"),
365                                 _("From:"), from,
366                                 _("Sent:"), sent_str,
367                                 _("To:"), to,
368                                 _("Subject:"), subject,
369                                 text);
370 }
371
372 gchar *
373 modest_text_utils_remove_mail_from_mail_list (const gchar *emails,
374                                               const gchar *email)
375 {
376         char *dup, *token, *ptr, *result;
377         GString *filtered_emails;
378
379         if (!emails)
380                 return NULL;
381
382         /* Search for substring */
383         if (!strstr ((const char *) emails, (const char *) email))
384                 return g_strdup (emails);
385
386         dup = g_strdup (emails);
387         filtered_emails = g_string_new (NULL);
388
389         token = strtok_r (dup, ",", &ptr);
390
391         while (token != NULL) {
392                 /* Add to list if not found */
393                 if (!strstr ((const char *) token, (const char *) email)) {
394                         if (G_UNLIKELY (filtered_emails->len) == 0)
395                                 g_string_append_printf (filtered_emails, "%s", token);
396                         else
397                                 g_string_append_printf (filtered_emails, ",%s", token);
398                 }
399                 token = strtok_r (NULL, ",", &ptr);
400         }
401         result = filtered_emails->str;
402
403         /* Clean */
404         g_free (dup);
405         g_string_free (filtered_emails, FALSE);
406
407         return result;
408 }