* bugfix: text not ending with a newline was not quoted correctly
[modest] / src / modest-text-utils.c
1 /* modest-ui.c */
2
3 /* insert (c)/licensing information) */
4
5 #include <gtk/gtk.h>
6 #include <string.h>
7
8
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 #endif /*HAVE_CONFIG_H*/
12
13
14 /* private */
15 static GString *
16 get_next_line(GtkTextBuffer *b, GtkTextIter *iter);
17
18 static int
19 get_indent_level(const char *l);
20
21 static void
22 unquote_line(GString *l);
23
24 static void
25 append_quoted(GString *buf, const int indent, const GString *str, const int cutpoint);
26
27 static int
28 get_breakpoint_utf8(const gchar *s, const gint indent, const gint limit);
29
30 static int
31 get_breakpoint_ascii(const gchar *s, const gint indent, const gint limit);
32
33 static int
34 get_breakpoint(const gchar *s, const gint indent, const gint limit);
35
36 static GString *
37 get_next_line(GtkTextBuffer *b, GtkTextIter *iter)
38 {
39         GtkTextIter iter2, iter3;
40         gchar *tmp;
41         GString *line;
42         
43         if (gtk_text_iter_is_end(iter))
44                 return g_string_new("");
45         
46         gtk_text_buffer_get_iter_at_line_offset(b,
47                         &iter2,
48                         gtk_text_iter_get_line(iter),
49                         gtk_text_iter_get_chars_in_line(iter) - 1
50                 );
51         iter3 = iter2;
52         gtk_text_iter_forward_char(&iter2);
53         if (gtk_text_iter_is_end(&iter2)) {
54                 tmp = gtk_text_buffer_get_text(b, &iter2, iter, FALSE);
55         } else {
56                 tmp = gtk_text_buffer_get_text(b, &iter3, iter, FALSE);
57         }
58         line = g_string_new(tmp);
59                 
60         gtk_text_iter_forward_line(iter);
61         
62         return line;
63 }
64
65 static int
66 get_indent_level(const char *l)
67 {
68         int indent = 0;
69         
70         while (l[0]) {
71                 if (l[0] == '>') {
72                         indent++;
73                         if (l[1] == ' ') {
74                                 l++;
75                         }
76                 } else {
77                         break;
78                 }
79                 l++;
80                 
81         }
82
83         /*      if we hit the signature marker "-- ", we return -(indent + 1). This
84         *       stops reformatting.
85         */
86         if (strcmp(l, "-- ") == 0) {
87                 return -1-indent;
88         } else {
89                 return indent;
90         }
91 }
92
93 static void
94 unquote_line(GString *l) {
95         GString *r;
96         gchar *p;
97         
98         p = l->str;
99         while (p[0]) {
100                 if (p[0] == '>') {
101                         if (p[1] == ' ') {
102                                 p++;
103                         }
104                 } else {
105                         break;
106                 }
107                 p++;
108         }
109         g_string_erase (l, 0, p - l->str);
110 }
111
112 static void
113 append_quoted(GString *buf, int indent, const GString *str, const int cutpoint) {
114         int i;
115         
116         indent = indent < 0? abs(indent) -1 : indent;
117         for (i=0; i<=indent; i++) {
118                 g_string_append(buf, "> ");
119         }
120         if (cutpoint > 0) {
121                 g_string_append_len(buf, str->str, cutpoint);
122         } else {
123                 g_string_append(buf, str->str);
124         }
125         g_string_append(buf, "\n");
126 }
127
128 static int
129 get_breakpoint_utf8(const gchar *s, gint indent, const gint limit) {
130         gint index = 0;
131         const gchar *pos, *last;
132         gunichar *uni;
133         
134         indent = indent < 0? abs(indent) -1 : indent;
135         
136         last = NULL;
137         pos = s;
138         uni = g_utf8_to_ucs4_fast(s, -1, NULL);
139         while (pos[0]) {
140                 if ((index + 2 * indent > limit) && last) {
141                         g_free(uni);
142                         return last - s;
143                 }
144                 if (g_unichar_isspace(uni[index])) {
145                         last = pos;
146                 }
147                 pos = g_utf8_next_char(pos);
148                 index++;
149         }
150         g_free(uni);
151         return strlen(s);
152 }
153
154 static int
155 get_breakpoint_ascii(const gchar *s, const gint indent, const gint limit) {
156         gint i, last;
157         
158         last = strlen(s);
159         if (last + 2 * indent < limit)
160                 return last;
161         
162         for ( i=strlen(s) ; i>0; i-- ) {
163                 if (s[i] == ' ') {
164                         if (i + 2 * indent <= limit) {
165                                 return i;
166                         } else {
167                                 last = i;
168                         }
169                 }
170         }
171         return last;
172 }
173
174 static int
175 get_breakpoint(const gchar *s, const gint indent, const gint limit) {
176         
177         if (g_utf8_validate(s, -1, NULL)) {
178                 return get_breakpoint_utf8(s, indent, limit);
179         } else { /* assume ASCII */
180                 g_warning("invalid UTF-8 in msg");
181                 return get_breakpoint_ascii(s, indent, limit);
182         }
183 }       
184
185 gchar *
186 modest_text_utils_quote(GtkTextBuffer *buf, const gchar *from, const time_t sent_date, const int limit)
187 {
188         GtkTextIter iter;
189         gint indent, breakpoint, rem_indent;
190         gchar sent_str[101];
191         GString *q, *l, *remaining; /* quoted msg, line */
192         
193
194         /* format sent_date */
195         strftime(sent_str, 100, "%c", localtime(&sent_date));
196         q = g_string_new("");
197         g_string_printf(q, "On %s, %s wrote:\n", sent_str, from);
198         
199         /* remaining will store the rest of the line if we have to break it */
200         remaining = g_string_new("");
201         gtk_text_buffer_get_iter_at_line(buf, &iter, 0);
202         do {
203                 l = get_next_line(buf, &iter);
204                 indent = get_indent_level(l->str);
205                 unquote_line(l);
206                 
207                 if (remaining->len) {
208                         if (l->len && indent == rem_indent) {
209                                 g_string_prepend(l, " ");
210                                 g_string_prepend(l, remaining->str);
211                         } else {
212                                 do {
213                                         breakpoint = get_breakpoint(remaining->str, rem_indent, limit);
214                                         append_quoted(q, rem_indent, remaining, breakpoint);
215                                         g_string_erase(remaining, 0, breakpoint);
216                                         if (remaining->str[0] == ' ') {
217                                                 g_string_erase(remaining, 0, 1);
218                                         }               
219                                 } while (remaining->len);
220                         }
221                 }
222                 g_string_free(remaining, TRUE);
223                 breakpoint = get_breakpoint(l->str, indent, limit);
224                 remaining = g_string_new(l->str + breakpoint);
225                 if (remaining->str[0] == ' ') {
226                         g_string_erase(remaining, 0, 1);
227                 }
228                 rem_indent = indent;
229                 append_quoted(q, indent, l, breakpoint);
230                 g_string_free(l, TRUE);
231         } while (remaining->str[0] || !gtk_text_iter_is_end(&iter));
232         
233         return g_string_free(q, FALSE);
234 }