1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  * Pan - A Newsreader for Gtk+
4  * Copyright (C) 2002-2006  Charles Kerr <charles@rebelbase.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include <config.h>
21 #include <cctype>
22 #include <cmath>
23 #include <iostream>
24 #include <sstream>
25 extern "C" {
26   #include <glib/gi18n.h>
27   #include "gtk-compat.h"
28   #include <gmime/gmime.h>
29 }
30 #include <gdk-pixbuf/gdk-pixbuf.h>
31 #include <pan/general/debug.h>
32 #include <pan/general/log.h>
33 #include <pan/general/macros.h>
34 #include <pan/general/utf8-utils.h>
35 #include <pan/general/e-util.h>
36 #include <pan/usenet-utils/gnksa.h>
37 #include <pan/usenet-utils/mime-utils.h>
38 #include <pan/usenet-utils/url-find.h>
39 #include <pan/icons/pan-pixbufs.h>
40 #include "body-pane.h"
41 #include "pad.h"
42 #include "tango-colors.h"
43 #include "xface.h"
44 #include "url.h"
45 #include "gtk-compat.h"
46 #include "save-attach-ui.h"
47 
48 #ifdef HAVE_WEBKIT
49   #include <webkit/webkit.h>
50 #endif
51 
52 #define FIRST_PICTURE "first-picture"
53 
54 #define FACE_SIZE 48
55 
56 using namespace pan;
57 
58 /***
59 ****
60 ***/
61 
62 namespace
63 {
64 
get_window(GtkWidget * w)65   GtkWindow* get_window (GtkWidget* w)
66   {
67     return GTK_WINDOW (gtk_widget_get_toplevel (w));
68   }
69 
70   enum Icons
71   {
72     ICON_SIG_OK,
73     ICON_SIG_FAIL,
74     NUM_ICONS
75   };
76 
77   struct Icon {
78   const guint8 * pixbuf_txt;
79   GdkPixbuf * pixbuf;
80   } icons[NUM_ICONS] = {
81     { icon_sig_ok,          0 },
82     { icon_sig_fail,        0 }
83   };
84 }
85 
86 /***
87 ****
88 ***/
89 
90 /**
91 ***  Pixbuf Cache
92 **/
93 namespace
94 {
95   class PixbufCache
96   {
97     private:
98       typedef std::pair<GdkPixbuf*,GdkPixbuf*> pixbuf_pair_t; // original, scaled
99       typedef std::map<int,pixbuf_pair_t> pixbufs_t;
100       pixbufs_t _pixbufs;
101 
102     public:
PixbufCache()103       PixbufCache() {}
~PixbufCache()104       ~PixbufCache() { clear(); }
105 
106     public:
clear()107       void clear () {
108         foreach (pixbufs_t, _pixbufs, it) {
109           g_object_unref (it->second.first);
110           g_object_unref (it->second.second);
111         }
112         _pixbufs.clear ();
113       }
114 
115     public:
get_pixbuf_at_offset(int offset,GdkPixbuf * & original,GdkPixbuf * & scaled)116       bool get_pixbuf_at_offset (int offset, GdkPixbuf*& original, GdkPixbuf*& scaled) {
117         if (!_pixbufs.count(offset))
118           return false;
119         pixbuf_pair_t &p(_pixbufs[offset]);
120         original = p.first;
121         scaled = p.second;
122         return true;
123       }
set_pixbuf_at_offset(int offset,GdkPixbuf * original,GdkPixbuf * scaled)124       void set_pixbuf_at_offset (int offset, GdkPixbuf *original, GdkPixbuf *scaled) {
125         const bool had_old (_pixbufs.count(offset));
126         pixbuf_pair_t &p(_pixbufs[offset]), old(p);
127         g_object_ref (p.first = original);
128         g_object_ref (p.second = scaled);
129         if (had_old) {
130           g_object_unref (old.first);
131           g_object_unref (old.second);
132         }
133       }
134   };
135 
delete_pixbuf_cache(gpointer pc)136   void delete_pixbuf_cache (gpointer pc) {
137     delete static_cast<PixbufCache*>(pc);
138   }
139 
get_pixbuf_cache(gpointer gp)140   PixbufCache& get_pixbuf_cache (gpointer gp) {
141     static const char * PIXBUF_CACHE ("pixbuf-cache");
142     GObject * o (G_OBJECT (gp));
143     PixbufCache *pc ((PixbufCache*) g_object_get_data(o, PIXBUF_CACHE));
144     if (!pc) {
145       pc = new PixbufCache ();
146       g_object_set_data_full (o, PIXBUF_CACHE, pc, delete_pixbuf_cache);
147     }
148     return *pc;
149   }
150 
get_pixbuf_at_offset(gpointer o,int offset,GdkPixbuf * & original,GdkPixbuf * & scaled)151   bool get_pixbuf_at_offset (gpointer o, int offset, GdkPixbuf*& original, GdkPixbuf*& scaled) {
152     return get_pixbuf_cache(o).get_pixbuf_at_offset (offset, original, scaled);
153   }
154 
set_pixbuf_at_offset(gpointer o,int offset,GdkPixbuf * original,GdkPixbuf * scaled)155   void set_pixbuf_at_offset (gpointer o, int offset, GdkPixbuf *original, GdkPixbuf *scaled) {
156     get_pixbuf_cache(o).set_pixbuf_at_offset (offset, original, scaled);
157   }
158 
clear_pixbuf_cache(gpointer o)159   void clear_pixbuf_cache (gpointer o) {
160     get_pixbuf_cache(o).clear ();
161   }
162 }
163 
164 /**
165 ***  "fullsize" flag
166 **/
167 namespace
168 {
169   #define FULLSIZE "fullsize"
170 
set_fullsize_flag(gpointer o,bool b)171   void set_fullsize_flag (gpointer o, bool b) {
172     g_object_set_data (G_OBJECT(o), FULLSIZE, GINT_TO_POINTER(b));
173   }
get_fullsize_flag(gpointer o)174   bool get_fullsize_flag (gpointer o) {
175     return g_object_get_data (G_OBJECT(o), FULLSIZE) != 0;
176   }
toggle_fullsize_flag(gpointer o)177   bool toggle_fullsize_flag (gpointer o) {
178     const bool b (!get_fullsize_flag (o));
179     set_fullsize_flag (o, b);
180     return b;
181   }
182 }
183 
184 /**
185 ***  Cursors
186 **/
187 namespace
188 {
189   enum {
190     CURSOR_IBEAM,
191     CURSOR_HREF,
192     CURSOR_ZOOM_IN,
193     CURSOR_ZOOM_OUT,
194     CURSOR_QTY
195   };
196 
197   GdkCursor ** cursors(0);
198   GdkCursor * cursor_current(0);
199 
free_cursors(void)200   void free_cursors (void) {
201     delete cursors;
202   }
203 
create_cursors()204   void create_cursors() {
205     cursors = new GdkCursor*[CURSOR_QTY];
206   }
207 
init_cursors(GtkWidget * w)208   void init_cursors (GtkWidget * w)
209   {
210       GdkDisplay * display (gtk_widget_get_display (w));
211 
212       int width, height;
213       const GtkIconSize size (GTK_ICON_SIZE_LARGE_TOOLBAR);
214 #if !GTK_CHECK_VERSION(3,0,0)
215       GtkStyle * style (gtk_widget_get_style (w));
216       const GtkTextDirection dir (GTK_TEXT_DIR_NONE);
217       const GtkStateType state (GTK_STATE_PRELIGHT);
218 
219       GtkIconSet * icon_set = gtk_style_lookup_icon_set (style, GTK_STOCK_ZOOM_IN);
220       GdkPixbuf * pixbuf = gtk_icon_set_render_icon (icon_set, style, dir, state, size, w, NULL);
221       g_object_get (G_OBJECT(pixbuf), "width", &width, "height", &height, NULL);
222       cursors[CURSOR_ZOOM_IN] = gdk_cursor_new_from_pixbuf (display, pixbuf, width/2, height/2);
223       g_object_unref (G_OBJECT(pixbuf));
224 
225       icon_set = gtk_style_lookup_icon_set (style, GTK_STOCK_ZOOM_OUT);
226       pixbuf = gtk_icon_set_render_icon (icon_set, style, dir, state, size, w, NULL);
227       g_object_get (G_OBJECT(pixbuf), "width", &width, "height", &height, NULL);
228       cursors[CURSOR_ZOOM_OUT] = gdk_cursor_new_from_pixbuf (display, pixbuf, width/2, height/2);
229       g_object_unref (G_OBJECT(pixbuf));
230 #else
231       GdkPixbuf * pixbuf = gtk_widget_render_icon_pixbuf (w, GTK_STOCK_ZOOM_IN, size);
232       g_object_get (G_OBJECT(pixbuf), "width", &width, "height", &height, NULL);
233       cursors[CURSOR_ZOOM_IN] = gdk_cursor_new_from_pixbuf (display, pixbuf, width/2, height/2);
234       g_object_unref (G_OBJECT(pixbuf));
235 
236       pixbuf = gtk_widget_render_icon_pixbuf (w, GTK_STOCK_ZOOM_OUT, size);
237       g_object_get (G_OBJECT(pixbuf), "width", &width, "height", &height, NULL);
238       cursors[CURSOR_ZOOM_OUT] = gdk_cursor_new_from_pixbuf (display, pixbuf, width/2, height/2);
239       g_object_unref (G_OBJECT(pixbuf));
240 #endif
241 
242       cursors[CURSOR_IBEAM] = gdk_cursor_new (GDK_XTERM);
243       cursors[CURSOR_HREF] = gdk_cursor_new (GDK_HAND2);
244   }
245 
set_cursor(GdkWindow * window,GtkWidget * w,int mode)246   void set_cursor (GdkWindow *window, GtkWidget *w, int mode)
247   {
248     init_cursors(w);
249     GdkCursor * cursor_new = cursors[mode];
250     if (cursor_new != cursor_current) {
251        gdk_window_set_cursor (window, cursor_current=cursor_new);
252        cursor_unref (cursor_current);
253     }
254   }
255 
set_cursor_from_iter(GdkWindow * window,GtkWidget * w,GtkTextIter * it)256   void set_cursor_from_iter (GdkWindow *window, GtkWidget *w, GtkTextIter *it)
257   {
258     GtkTextView * text_view (GTK_TEXT_VIEW (w));
259     GtkTextBuffer * buf (gtk_text_view_get_buffer (text_view));
260     GtkTextTagTable * tags (gtk_text_buffer_get_tag_table (buf));
261     GtkTextTag * pix_tag (gtk_text_tag_table_lookup (tags, "pixbuf"));
262     GtkTextTag * url_tag (gtk_text_tag_table_lookup (tags, "url"));
263     const bool in_url (gtk_text_iter_has_tag (it, url_tag));
264     const bool in_pix (gtk_text_iter_has_tag (it, pix_tag));
265     const bool fullsize (get_fullsize_flag (buf));
266 
267     int mode;
268     if (in_pix && fullsize) mode = CURSOR_ZOOM_OUT;
269     else if (in_pix)        mode = CURSOR_ZOOM_IN;
270     else if (in_url)        mode = CURSOR_HREF;
271     else                    mode = CURSOR_IBEAM;
272     set_cursor (window, w, mode);
273   }
274 }
275 
276 
277 namespace
278 {
get_named_tag_from_view(GtkWidget * w,const char * key)279   GtkTextTag* get_named_tag_from_view (GtkWidget * w, const char * key)
280   {
281     GtkTextView * text_view (GTK_TEXT_VIEW(w));
282     GtkTextBuffer * buf = gtk_text_view_get_buffer (text_view);
283     GtkTextTagTable * tags = gtk_text_buffer_get_tag_table (buf);
284     return gtk_text_tag_table_lookup (tags, key);
285   }
286 
get_iter_from_event_coords(GtkWidget * w,int x,int y,GtkTextIter * setme)287   void get_iter_from_event_coords (GtkWidget * w,
288                                    int x, int y,
289                                    GtkTextIter * setme)
290   {
291     GtkTextView * text_view (GTK_TEXT_VIEW (w));
292     gtk_text_view_window_to_buffer_coords (text_view,
293                                            GTK_TEXT_WINDOW_WIDGET,
294                                            x, y, &x, &y);
295     gtk_text_view_get_iter_at_location (text_view, setme, x, y);
296   }
297 
get_url_from_iter(GtkWidget * w,GtkTextIter * iter)298   std::string get_url_from_iter (GtkWidget * w, GtkTextIter * iter)
299   {
300     std::string ret;
301     GtkTextTag * url_tag (get_named_tag_from_view (w, "url"));
302     if (gtk_text_iter_has_tag (iter, url_tag))
303     {
304       GtkTextIter begin(*iter), end(*iter);
305       if (!gtk_text_iter_begins_tag (&begin, url_tag))
306         gtk_text_iter_backward_to_tag_toggle (&begin, NULL);
307       gtk_text_iter_forward_to_tag_toggle (&end, NULL);
308       char * pch = gtk_text_iter_get_text (&begin, &end);
309       if (pch) {
310         ret = pch;
311         g_free (pch);
312       }
313     }
314     return ret;
315   }
316 
motion_notify_event(GtkWidget * w,GdkEventMotion * event,gpointer hover_url)317   gboolean motion_notify_event (GtkWidget       * w,
318                                 GdkEventMotion  * event,
319                                 gpointer          hover_url)
320   {
321     if (event->window != NULL)
322     {
323       int x, y;
324       if (event->is_hint)
325         gdk_window_get_device_position (event->window, event->device, &x, &y, NULL);
326       else {
327         x = (int) event->x;
328         y = (int) event->y;
329       }
330       GtkTextIter iter;
331       get_iter_from_event_coords (w, x, y, &iter);
332       set_cursor_from_iter (event->window, w, &iter);
333       *static_cast<std::string*>(hover_url) = get_url_from_iter (w, &iter);
334     }
335 
336     return false;
337   }
338 
339   /* returns a GdkPixbuf of the scaled image.
340      unref it when no longer needed. */
size_to_fit(GdkPixbuf * pixbuf,const GtkAllocation * size)341   GdkPixbuf* size_to_fit (GdkPixbuf           * pixbuf,
342                           const GtkAllocation * size)
343   {
344     const int nw (size ? size->width : 0);
345     const int nh (size ? size->height : 0);
346 
347     GdkPixbuf * out (0);
348     if (nw>=100 && nh>=100)
349     {
350       const int ow (gdk_pixbuf_get_width (pixbuf));
351       const int oh (gdk_pixbuf_get_height (pixbuf));
352       double scale_factor (std::min (nw/(double)ow, nh/(double)oh));
353       scale_factor = std::min (scale_factor, 1.0);
354       const int scaled_width ((int) std::floor (ow * scale_factor + 0.5));
355       const int scaled_height ((int) std::floor (oh * scale_factor + 0.5));
356       out = gdk_pixbuf_scale_simple (pixbuf,
357                                      scaled_width, scaled_height,
358                                      GDK_INTERP_BILINEAR);
359     }
360 
361     if (!out)
362     {
363       g_object_ref (pixbuf);
364       out = pixbuf;
365     }
366 
367     return out;
368   }
369 
resize_picture_at_iter(GtkTextBuffer * buf,GtkTextIter * iter,bool fullsize,const GtkAllocation * size,GtkTextTag * apply_tag)370   void resize_picture_at_iter (GtkTextBuffer        * buf,
371                                GtkTextIter          * iter,
372                                bool                   fullsize,
373                                const GtkAllocation  * size,
374                                GtkTextTag           * apply_tag)
375   {
376 
377     const int begin_offset (gtk_text_iter_get_offset (iter));
378 
379     GdkPixbuf * original (0);
380     GdkPixbuf * old_scaled (0);
381     if (!get_pixbuf_at_offset (buf, begin_offset, original, old_scaled))
382       return;
383 
384     GdkPixbuf * new_scaled (size_to_fit (original, (fullsize ? 0 : size)));
385     const int old_w (gdk_pixbuf_get_width (old_scaled));
386     const int new_w (gdk_pixbuf_get_width (new_scaled));
387     const int old_h (gdk_pixbuf_get_height (old_scaled));
388     const int new_h (gdk_pixbuf_get_height (new_scaled));
389     if (old_w!=new_w || old_h!=new_h)
390     {
391       // remove the old..
392       GtkTextIter old_end (*iter);
393       gtk_text_iter_forward_to_tag_toggle (&old_end, apply_tag);
394       gtk_text_buffer_delete (buf, iter, &old_end);
395 
396       // insert the new..
397       gtk_text_buffer_insert_pixbuf (buf, iter, new_scaled);
398       gtk_text_buffer_insert (buf, iter, "\n", -1);
399       set_pixbuf_at_offset (buf, begin_offset, original, new_scaled);
400 
401       // and apply the tag.
402       GtkTextIter begin (*iter);
403       gtk_text_iter_set_offset (&begin, begin_offset);
404       gtk_text_buffer_apply_tag (buf, apply_tag, &begin, iter);
405     }
406 
407     g_object_unref (new_scaled);
408   }
409 }
410 
411 gboolean
mouse_button_pressed_cb(GtkWidget * w,GdkEventButton * e,gpointer p)412 BodyPane :: mouse_button_pressed_cb (GtkWidget *w, GdkEventButton *e, gpointer p)
413 {
414   return static_cast<BodyPane*>(p)->mouse_button_pressed (w, e);
415 }
416 
417 gboolean
mouse_button_pressed(GtkWidget * w,GdkEventButton * event)418 BodyPane :: mouse_button_pressed (GtkWidget *w, GdkEventButton *event)
419 {
420   g_return_val_if_fail (GTK_IS_TEXT_VIEW(w), false);
421 
422   if (event->button==1 || event->button==2) {
423     const std::string& url (_hover_url);
424     if (!url.empty()) {
425       /* this is a crude way of making sure that double-click
426        * doesn't open two or three browser windows. */
427       static time_t last_url_time (0);
428       const time_t this_url_time (time (0));
429       if (this_url_time != last_url_time) {
430         last_url_time = this_url_time;
431         URL :: open (_prefs, url.c_str());
432       }
433     } else { // maybe we're zooming in/out on a pic...
434       GtkTextIter iter;
435       GtkTextTag * pix_tag (get_named_tag_from_view (w, "pixbuf"));
436       get_iter_from_event_coords (w, (int)event->x, (int)event->y, &iter);
437       if (gtk_text_iter_has_tag (&iter, pix_tag))
438       {
439         if (!gtk_text_iter_begins_tag (&iter, pix_tag))
440           gtk_text_iter_backward_to_tag_toggle (&iter, pix_tag);
441         g_assert (gtk_text_iter_begins_tag (&iter, pix_tag));
442 
443         // percent_x,percent_y reflect where the user clicked in the picture
444         GdkRectangle rec;
445         gtk_text_view_get_iter_location (GTK_TEXT_VIEW(w), &iter, &rec);
446         int buf_x, buf_y;
447         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW(w), GTK_TEXT_WINDOW_WIDGET,
448                                                (gint)event->x, (gint)event->y, &buf_x, &buf_y);
449         const double percent_x = (buf_x - rec.x) / (double)rec.width;
450         const double percent_y = (buf_y - rec.y) / (double)rec.height;
451 
452          // resize the picture and refresh `iter'
453         const int offset (gtk_text_iter_get_offset (&iter));
454         GtkTextBuffer * buf (gtk_text_view_get_buffer (GTK_TEXT_VIEW(w)));
455         const bool fullsize (toggle_fullsize_flag (buf));
456         GtkAllocation aloc;
457         gtk_widget_get_allocation(w, &aloc);
458 //        std::cerr<<"457 alloc "<<aloc.x<<" "<<aloc.y<<" "<<aloc.width<<" "<<aloc.height<<"\n";
459         resize_picture_at_iter (buf, &iter, fullsize, &aloc, pix_tag);
460         gtk_text_iter_set_offset (&iter, offset);
461         set_cursor_from_iter (event->window, w, &iter);
462 
463         // x2,y2 are to position percent_x,percent_y in the middle of the window.
464         GtkTextMark * mark = gtk_text_buffer_create_mark (buf, NULL, &iter, true);
465         const double x2 = CLAMP ((percent_x + (percent_x - 0.5)), 0.0, 1.0);
466         const double y2 = CLAMP ((percent_y + (percent_y - 0.5)), 0.0, 1.0);
467         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW(w), mark, 0.0, true, x2, y2);
468         gtk_text_buffer_delete_mark (buf, mark);
469       }
470     }
471   }
472   return false;
473 }
474 
475 /***
476 ****  INIT
477 ***/
478 namespace
479 {
480 
get_or_create_tag(GtkTextTagTable * table,const char * key)481   GtkTextTag* get_or_create_tag (GtkTextTagTable * table, const char * key)
482   {
483     g_assert (table);
484     g_assert (key && *key);
485 
486     GtkTextTag * tag (gtk_text_tag_table_lookup (table, key));
487     if (!tag) {
488       tag = gtk_text_tag_new (key);
489       gtk_text_tag_table_add (table, tag);
490       g_object_unref (tag); // table refs it
491     }
492     return tag;
493   }
494 
495   void
set_text_buffer_tags(GtkTextBuffer * buffer,const Prefs & p)496   set_text_buffer_tags (GtkTextBuffer * buffer, const Prefs& p)
497   {
498     const PanColors& colors (PanColors::get());
499     const std::string fg (p.get_color_str ("text-color-fg", colors.def_fg));
500     const std::string bg (p.get_color_str ("text-color-bg", colors.def_bg));
501 
502     GtkTextTagTable * table = gtk_text_buffer_get_tag_table (buffer);
503 
504     get_or_create_tag (table, "pixbuf");
505     get_or_create_tag (table, "quote_0");
506 
507     g_object_set (get_or_create_tag (table, "text"),
508       "foreground", p.get_color_str ("text-color-fg", fg).c_str(),
509       "background", p.get_color_str ("text-color-bg", bg).c_str(),
510       NULL);
511     g_object_set (get_or_create_tag(table,"bold"),
512       "weight", PANGO_WEIGHT_BOLD,
513       NULL);
514     g_object_set (get_or_create_tag (table, "italic"),
515       "style", PANGO_STYLE_ITALIC,
516       NULL);
517     g_object_set (get_or_create_tag (table, "underline"),
518       "underline", PANGO_UNDERLINE_SINGLE,
519       NULL);
520     g_object_set (get_or_create_tag (table, "url"),
521       "underline", PANGO_UNDERLINE_SINGLE,
522       "foreground", p.get_color_str ("body-pane-color-url", TANGO_SKY_BLUE_DARK).c_str(),
523       "background", p.get_color_str ("body-pane-color-url-bg", bg).c_str(),
524       NULL);
525     g_object_set (get_or_create_tag (table, "quote_1"),
526       "foreground", p.get_color_str ("body-pane-color-quote-1", TANGO_CHAMELEON_DARK).c_str(),
527       "background", p.get_color_str ("body-pane-color-quote-1-bg", bg).c_str(),
528       NULL);
529     g_object_set (get_or_create_tag (table, "quote_2"),
530       "foreground", p.get_color_str ("body-pane-color-quote-2", TANGO_ORANGE_DARK).c_str(),
531       "background", p.get_color_str ("body-pane-color-quote-2-bg", bg).c_str(),
532       NULL);
533     g_object_set (get_or_create_tag (table, "quote_3"),
534       "foreground", p.get_color_str ("body-pane-color-quote-3", TANGO_PLUM_DARK).c_str(),
535       "background", p.get_color_str ("body-pane-color-quote-3-bg", bg).c_str(),
536       NULL);
537     g_object_set (get_or_create_tag (table, "signature"),
538       "foreground", p.get_color_str ("body-pane-color-signature", TANGO_SKY_BLUE_LIGHT).c_str(),
539       "background", p.get_color_str ("body-pane-color-signature-bg", bg).c_str(),
540       NULL);
541   }
542 }
543 
544 /***
545 ****
546 ***/
547 namespace
548 {
549   // handle up, down, pgup, pgdown to scroll
text_key_pressed(GtkWidget * w,GdkEventKey * event,gpointer scroll)550   gboolean text_key_pressed (GtkWidget * w, GdkEventKey * event, gpointer scroll)
551   {
552     gboolean handled (false);
553 
554     g_return_val_if_fail (GTK_IS_TEXT_VIEW(w), false);
555 
556     const bool up = event->keyval==GDK_KEY_Up || event->keyval==GDK_KEY_KP_Up;
557     const bool down = event->keyval==GDK_KEY_Down || event->keyval==GDK_KEY_KP_Down;
558 
559     if (up || down)
560     {
561       handled = true;
562       gtk_text_view_place_cursor_onscreen (GTK_TEXT_VIEW(w));
563       GtkAdjustment * adj = gtk_scrolled_window_get_vadjustment (
564                                                       GTK_SCROLLED_WINDOW(scroll));
565       gdouble val = gtk_adjustment_get_value(adj);
566       if (up)
567         val -= gtk_adjustment_get_step_increment(adj);
568       else
569         val += gtk_adjustment_get_step_increment(adj);
570       val = MAX(val, gtk_adjustment_get_lower(adj) );
571       val = MIN(val, gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj) );
572       gtk_adjustment_set_value (adj, val);
573     }
574 
575     return handled;
576   }
577 }
578 
579 /****
580 *****
581 *****   SETTING THE TEXT FROM AN ARTICLE
582 *****
583 ****/
584 namespace
585 {
text_get_show_all_headers()586   bool text_get_show_all_headers () { return false; }
587 
588   /**
589    * Returns the quote tag ("quote_0", "quote_1", etc) appropriate for the line.
590    * The right tag is calculated by adding up the number of quote characters
591    * at the beginning of the line.
592    *
593    * @param utf8_line the line whose quote status we're checking
594    * @param utf8_byte_len the byte length of utf8_line
595    * @return a const string for the line's quote tag.  Never NULL.
596    */
get_quote_tag(const TextMassager * text_massager,const char * utf8_line,int utf8_byte_len)597   const char * get_quote_tag (const TextMassager     * text_massager,
598                               const char             * utf8_line,
599                               int                      utf8_byte_len)
600   {
601     const char * str = utf8_line;
602     const char * line_end = utf8_line + utf8_byte_len;
603     const char * retval = "quote_0";
604 
605     if (0<utf8_byte_len && str && *str)
606     {
607       int depth = 0;
608 
609       // walk past leading spaces
610       while (str!=line_end && g_unichar_isspace(g_utf8_get_char(str)))
611         str = g_utf8_next_char (str);
612 
613       // count the number of spaces or quote characters
614       for (;;) {
615         if (str == line_end)
616           break;
617         else if (text_massager->is_quote_character (g_utf8_get_char (str)))
618           ++depth;
619         else if (!g_unichar_isspace(g_utf8_get_char(str)))
620           break;
621         str = g_utf8_next_char (str);
622       }
623 
624       if (!depth)
625         retval = "quote_0";
626       else switch (depth % 3) {
627         case 1: retval = "quote_1"; break;
628         case 2: retval = "quote_2"; break;
629         case 0: retval = "quote_3"; break;
630       }
631     }
632 
633     return retval;
634   }
635 
636   typedef std::map<std::string,GdkPixbuf*> pixbufs_t;
637 
638   // don't use this directly -- use get_emoticons()
639   pixbufs_t emoticon_pixbufs;
640 
free_emoticons()641   void free_emoticons ()
642   {
643     foreach_const (pixbufs_t, emoticon_pixbufs, it)
644       g_object_unref (it->second);
645   }
646 
get_emoticons()647   pixbufs_t& get_emoticons ()
648   {
649      return emoticon_pixbufs;
650   }
651 
create_emoticons()652  void create_emoticons()
653   {
654     emoticon_pixbufs[":)"] = gdk_pixbuf_new_from_inline (-1, icon_mozilla_smile, false, 0);
655     emoticon_pixbufs[":-)"] = gdk_pixbuf_new_from_inline (-1, icon_mozilla_smile, false, 0);
656     emoticon_pixbufs[";)"] = gdk_pixbuf_new_from_inline (-1, icon_mozilla_wink, false, 0);
657     emoticon_pixbufs[":("] = gdk_pixbuf_new_from_inline (-1, icon_mozilla_frown, false, 0);
658     emoticon_pixbufs[":P"] = gdk_pixbuf_new_from_inline (-1, icon_mozilla_tongueout, false, 0);
659     emoticon_pixbufs[":O"] = gdk_pixbuf_new_from_inline (-1, icon_mozilla_surprised, false, 0);
660   }
661 
662   enum TagMode { ADD, REPLACE };
663 
664   void
set_section_tag(GtkTextBuffer * buffer,GtkTextIter * start,const StringView & body,const StringView & area,const char * tag,TagMode mode)665   set_section_tag (GtkTextBuffer     * buffer,
666                    GtkTextIter       * start,
667                    const StringView  & body,
668                    const StringView  & area,
669                    const char        * tag,
670                    TagMode             mode)
671   {
672     // if no alnums, chances are area is a false positive
673     int alnums (0);
674     for (const char *pch(area.begin()), *end(area.end()); pch!=end; ++pch)
675       if (::isalnum(*pch))
676         ++alnums;
677     if (!alnums)
678       return;
679 
680     GtkTextIter mark_start = *start;
681     gtk_text_iter_forward_chars (&mark_start,
682                                  g_utf8_strlen(body.str, area.str-body.str));
683     GtkTextIter mark_end = mark_start;
684     gtk_text_iter_forward_chars (&mark_end, g_utf8_strlen(area.str,area.len));
685     if (mode == REPLACE)
686       gtk_text_buffer_remove_all_tags (buffer, &mark_start, &mark_end);
687     gtk_text_buffer_apply_tag_by_name (buffer, tag, &mark_start, &mark_end);
688   }
689 
690   void
show_signature(GtkTextBuffer * buffer,GtkTextMark * mark,std::string & body,bool show)691   show_signature (GtkTextBuffer      * buffer,
692                     GtkTextMark        * mark,
693                     std::string        & body,
694                     bool                show)
695   {
696     GtkTextTagTable * tags (gtk_text_buffer_get_tag_table (buffer));
697     if (tags)
698     {
699       GtkTextTag * sig_tag (gtk_text_tag_table_lookup (tags, "signature"));
700       if (sig_tag)
701         g_object_set (sig_tag, "invisible", !show, NULL);
702     }
703   }
704 
705   void
replace_emoticon_text_with_pixbuf(GtkTextBuffer * buffer,GtkTextMark * mark,std::string & body,const std::string & text,GdkPixbuf * pixbuf)706   replace_emoticon_text_with_pixbuf (GtkTextBuffer      * buffer,
707                                      GtkTextMark        * mark,
708                                      std::string        & body,
709                                      const std::string  & text,
710                                      GdkPixbuf          * pixbuf)
711   {
712     g_assert (!text.empty());
713     g_assert (pixbuf != 0);
714 
715     GtkTextTagTable * tags (gtk_text_buffer_get_tag_table (buffer));
716     GtkTextTag * url_tag (gtk_text_tag_table_lookup (tags, "url"));
717 
718     const size_t n (text.size());
719     std::string::size_type pos (0);
720     while (((pos=body.find(text,pos))) != body.npos)
721     {
722       GtkTextIter begin;
723       gtk_text_buffer_get_iter_at_mark (buffer, &begin, mark);
724       gtk_text_iter_forward_chars (&begin, g_utf8_strlen(&body[0],pos));
725 
726       if (gtk_text_iter_has_tag (&begin, url_tag))
727         pos += n;
728       else {
729         GtkTextIter end (begin);
730         gtk_text_iter_forward_chars (&end, n);
731         gtk_text_buffer_delete (buffer, &begin, &end);
732         body.erase (pos, text.size());
733         gtk_text_buffer_insert_pixbuf (buffer, &end, pixbuf);
734         body.insert (pos, 1, '?'); // make body.size() match the textbuf's size
735       }
736     }
737   }
738 
739   /**
740    * Appends the specified body into the text buffer.
741    * This function takes care of muting quotes and marking
742    * quoted and URL areas in the GtkTextBuffer.
743    */
744   void
append_text_buffer_nolock(const TextMassager * text_massager,GtkTextBuffer * buffer,const StringView & body_in,bool mute_quotes,bool show_smilies,bool show_sig,bool do_markup,bool do_urls)745   append_text_buffer_nolock (const TextMassager  * text_massager,
746                              GtkTextBuffer       * buffer,
747                              const StringView    & body_in,
748                              bool                  mute_quotes,
749                              bool                  show_smilies,
750                              bool                  show_sig,
751                              bool                  do_markup,
752                              bool                  do_urls)
753   {
754 
755     g_return_if_fail (buffer!=0);
756     g_return_if_fail (GTK_IS_TEXT_BUFFER(buffer));
757 
758     // mute the quoted text, if desired
759     std::string body;
760     if (mute_quotes)
761       body = text_massager->mute_quotes (body_in);
762     else
763       body.assign (body_in.str, body_in.len);
764 
765     // insert the text
766     GtkTextIter end;
767     gtk_text_buffer_get_end_iter (buffer, &end);
768     GtkTextMark * mark = gtk_text_buffer_create_mark (buffer, "blah", &end, true);
769     gtk_text_buffer_insert (buffer, &end, body.c_str(), -1);
770     GtkTextIter start;
771     gtk_text_buffer_get_iter_at_mark (buffer, &start, mark);
772 
773     StringView v(body), line;
774     GtkTextIter mark_start (start);
775 
776     // colorize text
777     gtk_text_buffer_apply_tag_by_name (buffer, "text", &mark_start, &end);
778 
779     // find where the signature begins...
780     const char * sig_point (0);
781     int offset (0);
782     if (GNKSA::find_signature_delimiter (v, offset) != GNKSA::SIG_NONE)
783       sig_point = v.str + offset;
784 
785     // colorize the quoted text
786     GtkTextIter mark_end;
787     std::string last_quote_tag;
788     bool is_sig (false);
789     const char * last_quote_begin (v.str);
790     while (v.pop_token (line, '\n'))
791     {
792       if (line.empty())
793         continue;
794 
795       if (line.str == sig_point)
796         is_sig = true;
797 
798       const std::string quote_tag = is_sig
799         ?  "signature"
800         : get_quote_tag (text_massager, line.str, line.len);
801 
802       // if we've changed tags, colorize the previous block
803       if (!last_quote_tag.empty() && quote_tag!=last_quote_tag) {
804         mark_end = mark_start;
805         gtk_text_iter_forward_chars (&mark_end, g_utf8_strlen(last_quote_begin,line.str-1-last_quote_begin));
806         gtk_text_buffer_apply_tag_by_name (buffer, last_quote_tag.c_str(), &mark_start, &mark_end);
807         mark_start = mark_end;
808         gtk_text_iter_forward_chars (&mark_start, 1);
809         last_quote_begin = line.str;
810       }
811 
812       last_quote_tag = quote_tag;
813     }
814 
815     // apply the final tag, if there is one */
816     if (!last_quote_tag.empty()) {
817       gtk_text_buffer_get_end_iter (buffer, &mark_end);
818       gtk_text_buffer_apply_tag_by_name (buffer, last_quote_tag.c_str(), &mark_start, &mark_end);
819     }
820 
821     // apply markup
822     const StringView v_all (body);
823     if (do_markup)
824     {
825       bool is_patch = false;
826       std::set<const char*> already_processed;
827       StringView v(body), line;
828       while (v.pop_token (line, '\n'))
829       {
830         // if we detect that this message contains a patch,
831         // turn off markup for the rest of the article.
832         if (!is_patch)
833           is_patch = (line.strstr("--- ")==line.str) || (line.strstr("@@ ")==line.str);
834         if (is_patch)
835           continue;
836 
837         for (;;)
838         {
839           // find the first markup character.
840           // if we've already used it,
841           // or if it was preceeded by something not a space or punctuation,
842           // then keep looking.
843           const char * b (line.strpbrk ("_*/"));
844           if (!b)
845             break;
846           if (already_processed.count(b) ||
847               (b!=&line.front() && !isspace(b[-1]) && !ispunct(b[-1]))) {
848             line.eat_chars (b+1-line.str);
849             continue;
850           }
851 
852           // find the ending corresponding markup character.
853           // if it was followed by something not a space or punctuation, keep looking.
854           const char * e = b;
855           while ((e = line.strchr (*b, 1+(e-line.str)))) {
856             if (e==&line.back() || isspace(e[1]) || ispunct(e[1]) || strchr("_*/", e[1]))
857               break;
858           }
859 
860           if (e) {
861             already_processed.insert (e);
862             const char * type (0);
863             switch (*b) {
864               case '*': type = "bold"; break;
865               case '_': type = "underline"; break;
866               case '/': type = "italic"; break;
867             }
868             set_section_tag (buffer, &start, v_all, StringView(b,e+1), type, ADD);
869           }
870 
871           line.eat_chars (b+1-line.str);
872         }
873       }
874     }
875 
876     // colorize urls
877     if (do_urls) {
878       StringView area;
879       StringView march (v_all);
880       while ((url_find (march, area))) {
881         set_section_tag (buffer, &start, v_all, area, "url", REPLACE);
882         march = march.substr (area.str + area.len, 0);
883       }
884     }
885 
886     // do this last, since it alters the text instead of just marking it up
887     if (show_smilies) {
888       pixbufs_t& emoticons (get_emoticons());
889       foreach_const (pixbufs_t, emoticons, it)
890         replace_emoticon_text_with_pixbuf (buffer, mark, body, it->first, it->second);
891     }
892 
893     show_signature (buffer, mark, body, show_sig);
894 
895     gtk_text_buffer_delete_mark (buffer, mark);
896   }
897 
898   /**
899    * Generates a GtkPixmap object from a given GMimePart that contains an image.
900    * Used for displaying attached pictures inline.
901    */
get_pixbuf_from_gmime_part(GMimePart * part)902   GdkPixbuf* get_pixbuf_from_gmime_part (GMimePart * part)
903   {
904     GdkPixbufLoader * l (gdk_pixbuf_loader_new ());
905     GError * err (0);
906 
907     // populate the loader
908 #ifdef HAVE_GMIME_30
909     GMimeDataWrapper * wrapper (g_mime_part_get_content (part));
910 #else
911     GMimeDataWrapper * wrapper (g_mime_part_get_content_object (part));
912 #endif
913     if (wrapper)
914     {
915       GMimeStream * mem_stream (g_mime_stream_mem_new ());
916       g_mime_data_wrapper_write_to_stream (wrapper, mem_stream);
917       GByteArray * buffer (GMIME_STREAM_MEM(mem_stream)->buffer);
918       gsize bytesLeft = buffer->len;
919       guchar * data = buffer->data;
920 
921       // ticket #467446 - workaround gdkpixbuf <= 2.12.x's
922       // jpg loader bug (#494667) by feeding the loader in
923       // smaller chunks
924       while( bytesLeft > 0 )
925       {
926           const gsize n = MIN( 4096, bytesLeft );
927 
928           gdk_pixbuf_loader_write (l, data, n, &err);
929           if (err) {
930             Log::add_err (err->message);
931             g_clear_error (&err);
932             break;
933           }
934 
935           bytesLeft -= n;
936           data += n;
937       }
938 
939       g_object_unref (mem_stream);
940     }
941 
942     // flush the loader
943     gdk_pixbuf_loader_close (l, &err);
944     if (err) {
945       Log::add_err (err->message);
946       g_clear_error (&err);
947     }
948 
949     // create the pixbuf
950     GdkPixbuf * pixbuf (0);
951     if (!err)
952       pixbuf = gdk_pixbuf_loader_get_pixbuf (l);
953     else {
954       Log::add_err (err->message);
955       g_clear_error (&err);
956     }
957 
958     // cleanup
959     if (pixbuf)
960       g_object_ref (G_OBJECT(pixbuf));
961     g_object_unref (G_OBJECT(l));
962     return pixbuf;
963   }
964 
965 }
966 
967 void
append_part(GMimeObject * parent,GMimeObject * obj,GtkAllocation * widget_size)968 BodyPane :: append_part (GMimeObject * parent, GMimeObject * obj, GtkAllocation * widget_size)
969 {
970   bool is_done (false);
971 
972   // we only need leaf parts..
973   if (!GMIME_IS_PART (obj))
974     return;
975 
976   GMimePart * part = GMIME_PART (obj);
977   GMimeContentType * type = g_mime_object_get_content_type (GMIME_OBJECT (part));
978 
979   // decide whether or not this part is a picture
980   bool is_image (g_mime_content_type_is_type (type, "image", "*"));
981   if (!is_image && g_mime_content_type_is_type(type, "application", "octet-stream")) {
982     const char *type, *subtype;
983     mime::guess_part_type_from_filename(g_mime_part_get_filename(part), &type, &subtype);
984     is_image = type && !strcmp(type,"image");
985   }
986 
987   // if it's a picture, draw it
988   if (is_image)
989   {
990     GdkPixbuf * original (get_pixbuf_from_gmime_part (part));
991     const bool fullsize (!_prefs.get_flag ("size-pictures-to-fit", true));
992     GdkPixbuf * scaled (size_to_fit (original, fullsize ? 0 : widget_size));
993 
994     if (scaled != 0)
995     {
996       GtkTextIter iter;
997 
998       // if this is the first thing in the buffer, precede it with a linefeed.
999       gtk_text_buffer_get_end_iter (_buffer, &iter);
1000       if (gtk_text_buffer_get_char_count (_buffer) > 0)
1001         gtk_text_buffer_insert (_buffer, &iter, "\n", -1);
1002 
1003       // rembember the location of the first picture.
1004       if (gtk_text_buffer_get_mark (_buffer, FIRST_PICTURE) == NULL)
1005         gtk_text_buffer_create_mark (_buffer, FIRST_PICTURE, &iter, true);
1006         gtk_text_buffer_create_mark (_buffer, FIRST_PICTURE, &iter, true);
1007 
1008       // add the picture
1009       const int begin_offset (gtk_text_iter_get_offset (&iter));
1010       gtk_text_buffer_insert_pixbuf (_buffer, &iter, scaled);
1011       gtk_text_buffer_insert (_buffer, &iter, "\n", -1);
1012       GtkTextIter iter_begin (iter);
1013       gtk_text_iter_set_offset (&iter_begin, begin_offset);
1014 
1015       // hook onto the tag a reference to the original picture
1016       // so that we can resize it later if user resizes the text pane.
1017       set_pixbuf_at_offset (_buffer, begin_offset, original, scaled);
1018       set_fullsize_flag (_buffer, fullsize);
1019 
1020       GtkTextTagTable * tags (gtk_text_buffer_get_tag_table (_buffer));
1021       GtkTextTag * tag (gtk_text_tag_table_lookup (tags, "pixbuf"));
1022       gtk_text_buffer_apply_tag (_buffer, tag, &iter_begin, &iter);
1023 
1024       g_object_unref (scaled);
1025       g_object_unref (original);
1026 
1027       is_done = true;
1028     }
1029   }
1030 
1031   // or, if it's text, display it
1032   else if (g_mime_content_type_is_type (type, "text", "*"))
1033   {
1034     const char * fallback_charset (_charset.c_str());
1035     const char * p_flowed (g_mime_object_get_content_type_parameter(obj,"format"));
1036     const bool flowed (g_strcmp0 (p_flowed, "flowed") == 0);
1037     std::string str = mime_part_to_utf8 (part, fallback_charset);
1038 
1039     if (!str.empty() && _prefs.get_flag ("wrap-article-body", false))
1040       str = _tm.fill (str, flowed);
1041 
1042     const bool do_mute (_prefs.get_flag ("mute-quoted-text", false));
1043     const bool do_smilies (_prefs.get_flag ("show-smilies-as-graphics", true));
1044     const bool do_markup (_prefs.get_flag ("show-text-markup", true));
1045     const bool do_urls (_prefs.get_flag ("highlight-urls", true));
1046     const bool do_sig(_prefs.get_flag ("show-article-sig", true));
1047     append_text_buffer_nolock (&_tm, _buffer, str, do_mute, do_smilies, do_sig, do_markup, do_urls);
1048     is_done = true;
1049   }
1050 
1051   // picture or otherwise, add to list of attachments
1052   if (is_image || !is_done) {
1053     const char * filename = g_mime_part_get_filename (part);
1054     char * pch = (filename && *filename)
1055       ? g_strdup_printf ("%s", filename)
1056       : g_strdup_printf (_("Unnamed File"));
1057 
1058     add_attachment_to_toolbar (pch);
1059 
1060     _attach_names.insert(pch);
1061   }
1062 }
1063 
1064 void
foreach_part_cb(GMimeObject * parent,GMimeObject * o,gpointer self)1065 BodyPane :: foreach_part_cb (GMimeObject* parent, GMimeObject* o, gpointer self)
1066 {
1067   BodyPane * pane = static_cast<BodyPane*>(self);
1068   GtkWidget * w (pane->_text);
1069   GtkAllocation aloc;
1070   gtk_widget_get_allocation(w, &aloc);
1071   pane->append_part (parent, o, &aloc);
1072 }
1073 
1074 
1075 /***
1076 ****  HEADERS
1077 ***/
1078 namespace
1079 {
add_bold_header_value(std::string & s,GMimeMessage * message,const char * key,const char * fallback_charset)1080   void add_bold_header_value (std::string   & s,
1081                               GMimeMessage  * message,
1082                               const char    * key,
1083                               const char    * fallback_charset)
1084   {
1085     const char * val (message ? g_mime_object_get_header ((GMimeObject *)message, key) : "");
1086     const std::string utf8_val (header_to_utf8 (val, fallback_charset));
1087     char * e (0);
1088     if (strcmp (key, "From"))
1089       e = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>", utf8_val.c_str());
1090     else {
1091       const StringView v = GNKSA :: get_short_author_name (utf8_val);
1092       e = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>", v.to_string().c_str());
1093     }
1094     s += e;
1095     g_free (e);
1096   }
1097 
add_header_line(std::string & s,const char * key_i18n,const char * key,const char * val,const char * fallback_charset)1098   size_t add_header_line (std::string  & s,
1099                           const char   * key_i18n,
1100                           const char   * key,
1101                           const char   * val,
1102                           const char   * fallback_charset)
1103   {
1104     char * e;
1105     e = g_markup_printf_escaped ("<span weight=\"bold\">%s:</span> ", key_i18n);
1106     s += e;
1107     g_free (e);
1108     const std::string utf8_val (header_to_utf8 (val, fallback_charset));
1109     e = g_markup_printf_escaped ("%s\n", utf8_val.c_str());
1110     s += e;
1111     const size_t retval (g_utf8_strlen(key,-1) + g_utf8_strlen(utf8_val.c_str(),-1) + 2);
1112     g_free (e);
1113 
1114     return retval;
1115   }
1116 
add_header_line(std::string & s,GMimeMessage * msg,const char * key_i18n,const char * key,const char * fallback_charset)1117   size_t add_header_line (std::string   & s,
1118                           GMimeMessage  * msg,
1119                           const char    * key_i18n,
1120                           const char    * key,
1121                           const char    * fallback_charset)
1122   {
1123     const char * val (msg ? g_mime_object_get_header ((GMimeObject *) msg, key) : "");
1124     return add_header_line (s, key_i18n, key, val, fallback_charset);
1125   }
1126 
1127 }
1128 
1129 void
set_text_from_message(GMimeMessage * message)1130 BodyPane :: set_text_from_message (GMimeMessage * message)
1131 {
1132   const char * fallback_charset (_charset.empty() ? 0 : _charset.c_str());
1133 
1134   // mandatory headers...
1135   std::string s;
1136   size_t w(0), l(0);
1137   l = add_header_line (s, message, _("Subject"), "Subject", fallback_charset);
1138   w = std::max (w, l);
1139   l = add_header_line (s, message, _("From"), "From", fallback_charset);
1140   w = std::max (w, l);
1141   l = add_header_line (s, message, _("Date"), "Date", fallback_charset);
1142   w = std::max (w, l);
1143 
1144   // conditional headers...
1145   if (message) {
1146     const StringView newsgroups (g_mime_object_get_header ((GMimeObject *) message, "Newsgroups"));
1147     if (newsgroups.strchr(',')) {
1148       l = add_header_line (s, message, _("Newsgroups"), "Newsgroups", fallback_charset);
1149       w = std::max (w, l);
1150     }
1151 	const StringView user_agent(
1152 			g_mime_object_get_header((GMimeObject *) message,
1153 					"User-Agent"));
1154 	if (!user_agent.empty()) {
1155 		l = add_header_line(s, message, _("User-Agent"), "User-Agent",
1156 				fallback_charset);
1157 		w = std::max(w, l);
1158 	}
1159 	const StringView x_newsreader(
1160 			g_mime_object_get_header((GMimeObject *) message,
1161 					"X-Newsreader"));
1162 	if (!x_newsreader.empty()) {
1163 		l = add_header_line(s, message, _("User-Agent"), "X-Newsreader",
1164 				fallback_charset);
1165 		w = std::max(w, l);
1166 	}
1167 	const StringView x_mailer(
1168 			g_mime_object_get_header((GMimeObject *) message, "X-Mailer"));
1169 	if (!x_mailer.empty()) {
1170 		l = add_header_line(s, message, _("User-Agent"), "X-Mailer",
1171 				fallback_charset);
1172 		w = std::max(w, l);
1173 	}
1174     const StringView followup_to (g_mime_object_get_header ((GMimeObject *) message, "Followup-To"));
1175     if (!followup_to.empty() && (followup_to!=newsgroups)) {
1176       l = add_header_line (s, message, _("Followup-To"), "Followup-To", fallback_charset);
1177       w = std::max (w, l);
1178     }
1179     const StringView reply_to (g_mime_object_get_header ((GMimeObject *) message, "Reply-To"));
1180     if (!reply_to.empty()) {
1181       const StringView from (g_mime_object_get_header ((GMimeObject *) message, "From"));
1182       StringView f_addr, f_name, rt_addr, rt_name;
1183       GNKSA :: do_check_from (from, f_addr, f_name, false);
1184       GNKSA :: do_check_from (reply_to, rt_addr, rt_name, false);
1185       if (f_addr != rt_addr) {
1186         l = add_header_line (s, message, _("Reply-To"), "Reply-To", fallback_charset);
1187         w = std::max (w, l);
1188       }
1189     }
1190   }
1191 
1192   s.resize (std::max((size_t)0,s.size()-1)); // remove trailing linefeed
1193   gtk_label_set_markup (GTK_LABEL(_headers), s.c_str());
1194 
1195 #if !GTK_CHECK_VERSION(3,0,0)
1196   // ellipsize mode is useless w/o this in expander...
1197   gtk_label_set_width_chars (GTK_LABEL(_headers), (int)w);
1198 #endif
1199 
1200   // set the x-face...
1201   gtk_image_clear(GTK_IMAGE(_xface));
1202   const char * pch = message ? g_mime_object_get_header ((GMimeObject *) message, "X-Face") : 0;
1203   if (pch && gtk_widget_get_window(_xface) )
1204   {
1205     gtk_widget_set_size_request (_xface, FACE_SIZE, FACE_SIZE);
1206     GdkPixbuf *pixbuf = NULL;
1207     pixbuf = pan_gdk_pixbuf_create_from_x_face (pch);
1208     gtk_image_set_from_pixbuf (GTK_IMAGE(_xface), pixbuf);
1209     g_object_unref (pixbuf);
1210   } else {
1211     gtk_widget_set_size_request(_xface, 0, FACE_SIZE);
1212   }
1213 
1214   // set the face
1215   gtk_image_clear(GTK_IMAGE(_face));
1216   pch = message ? g_mime_object_get_header ((GMimeObject *) message, "Face") : 0;
1217   if (pch && gtk_widget_get_window(_face))
1218   {
1219     gtk_widget_set_size_request (_face, FACE_SIZE, FACE_SIZE);
1220     GMimeEncoding dec;
1221     g_mime_encoding_init_decode(&dec, GMIME_CONTENT_ENCODING_BASE64);
1222     guchar* buf = new guchar[strlen(pch)];
1223     int len = g_mime_encoding_step(&dec, pch, strlen(pch), (char*)buf);
1224     GdkPixbufLoader *pl = gdk_pixbuf_loader_new_with_type( "png", NULL);
1225     gdk_pixbuf_loader_write(pl, buf, len, NULL);
1226     gdk_pixbuf_loader_close(pl, NULL);
1227     GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(pl);
1228     gtk_image_set_from_pixbuf (GTK_IMAGE(_face), pixbuf);
1229     g_object_unref(pl);
1230     delete[] buf;
1231   } else {
1232     gtk_widget_set_size_request(_face, 0, FACE_SIZE);
1233   }
1234 
1235   // set the terse headers...
1236   s.clear ();
1237   add_bold_header_value (s, message, "Subject", fallback_charset);
1238   s += _(" from ");
1239   add_bold_header_value (s, message, "From", fallback_charset);
1240   s += _(" at ");
1241   add_bold_header_value (s, message, "Date", fallback_charset);
1242   gtk_label_set_markup (GTK_LABEL(_terse), s.c_str());
1243   // ellipsize mode is useless w/o this in expander...
1244   gtk_label_set_width_chars (GTK_LABEL(_terse), (int)s.size());
1245 
1246   // clear the text buffer...
1247   GtkTextIter start, end;
1248   gtk_text_buffer_get_bounds (_buffer, &start, &end);
1249   gtk_text_buffer_delete (_buffer, &start, &end);
1250   if (gtk_text_buffer_get_mark (_buffer, FIRST_PICTURE) != NULL)
1251     gtk_text_buffer_delete_mark_by_name (_buffer, FIRST_PICTURE);
1252   clear_pixbuf_cache (_buffer);
1253 
1254   // maybe add the headers
1255   const bool do_show_headers (_prefs.get_flag ("show-all-headers", false));
1256   if (message && do_show_headers) {
1257 #ifdef HAVE_GMIME_30
1258     char * headers (g_mime_object_get_headers ((GMimeObject *) message, NULL));
1259 #else
1260     char * headers (g_mime_object_get_headers ((GMimeObject *) message));
1261 #endif
1262     GtkTextIter end;
1263     gtk_text_buffer_get_end_iter (_buffer, &end);
1264     StringView line, v(headers);
1265     while (v.pop_token (line, '\n')) {
1266       const std::string h (header_to_utf8 (line, fallback_charset));
1267       gtk_text_buffer_insert (_buffer, &end, h.c_str(), h.size());
1268       gtk_text_buffer_insert (_buffer, &end, "\n", 1);
1269     }
1270     gtk_text_buffer_insert (_buffer, &end, "\n", 1);
1271     g_free (headers);
1272   }
1273 
1274   clear_attachments();
1275 
1276   // FIXME: need to set a mark here so that when user hits follow-up,
1277   // the all-headers don't get included in the followup
1278 
1279   // set the text buffer...
1280   if (message)
1281     g_mime_message_foreach (message, foreach_part_cb, this);
1282   // set the html view
1283   GtkTextBuffer* buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(_text));
1284   GtkTextIter _start, _end;
1285   gtk_text_buffer_get_bounds (buffer, &_start, &_end);
1286 #ifdef HAVE_WEBKIT
1287   char* buf (gtk_text_buffer_get_text (buffer, &_start, &_end, false));
1288   if (buf) set_html_text(buf);
1289 #endif
1290 
1291   // if there was a picture, scroll to it.
1292   // otherwise scroll to the top of the body.
1293   GtkTextMark * mark = gtk_text_buffer_get_mark (_buffer, FIRST_PICTURE);
1294   if (mark && _prefs.get_flag ("focus-on-image", true))
1295     gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW(_text), mark, 0.0, true, 0.0, 0.0);
1296   else {
1297     GtkTextIter iter;
1298     gtk_text_buffer_get_start_iter  (_buffer, &iter);
1299     gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW(_text), &iter, 0.0, true, 0.0, 0.0);
1300   }
1301 }
1302 
1303 void
refresh()1304 BodyPane :: refresh ()
1305 {
1306   set_text_from_message (_message);
1307 }
1308 
1309 #ifdef HAVE_GMIME_CRYPTO
1310 gboolean
on_verbose_tooltip_cb(GtkWidget * widget,gint x,gint y,gboolean keyboard_tip,GtkTooltip * tooltip,gpointer data)1311 BodyPane :: on_verbose_tooltip_cb(GtkWidget  *widget,
1312                                   gint        x,
1313                                   gint        y,
1314                                   gboolean    keyboard_tip,
1315                                   GtkTooltip *tooltip,
1316                                   gpointer    data)
1317 {
1318   BodyPane* pane = static_cast<BodyPane*>(data);
1319   if (!pane) return false;
1320 
1321   gtk_tooltip_set_icon_from_stock (tooltip, GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG);
1322 
1323   GPGDecErr& err = pane->_gpgerr;
1324   GPGSignersInfo& info = err.signers;
1325   if (err.no_sigs) return false;
1326   if (info.signers.empty()) return false;
1327 
1328   EvolutionDateMaker ed;
1329 
1330   char buf[2048];
1331   std::pair<std::string,std::string> name_and_email = get_email_address(info.signers[0].name);
1332   g_snprintf(buf, sizeof(buf),
1333              _("<u>This is a <b>PGP-Signed</b> message.</u>\n\n"
1334              "<b>Signer:</b> %s (“%s”)\n"
1335              "<b>Valid until:</b> %s\n"
1336              "<b>Created on:</b> %s"),
1337              name_and_email.first.c_str(), name_and_email.second.c_str(),
1338              (info.signers[0].never_expires ? _("always") : ed.get_date_string(info.signers[0].expires)),
1339              ed.get_date_string(info.signers[0].created)
1340              );
1341 
1342   gtk_tooltip_set_markup (tooltip, buf);
1343 
1344   return true;
1345 }
1346 #endif
1347 
1348 void
set_article(const Article & a)1349 BodyPane :: set_article (const Article& a)
1350 {
1351   _article = a;
1352 
1353   if (_message)
1354     g_object_unref (_message);
1355 #ifdef HAVE_GMIME_CRYPTO
1356   _gpgerr.clear();
1357   _message = _cache.get_message (_article.get_part_mids(), _gpgerr);
1358 #else
1359   _message = _cache.get_message (_article.get_part_mids());
1360 #endif
1361 
1362   refresh ();
1363 
1364   set_cleared(false);
1365 
1366   _data.mark_read (_article);
1367 }
1368 
1369 void
clear()1370 BodyPane :: clear ()
1371 {
1372   if (_message)
1373     g_object_unref (_message);
1374   _message = 0;
1375 
1376   set_cleared(true);
1377 
1378   refresh ();
1379 }
1380 
1381 void
select_all()1382 BodyPane :: select_all ()
1383 {
1384   GtkTextIter start, end;
1385   gtk_text_buffer_get_bounds (_buffer, &start, &end);
1386   gtk_text_buffer_select_range (_buffer, &start, &end);
1387 }
1388 
1389 void
rot13_selected_text()1390 BodyPane :: rot13_selected_text ()
1391 {
1392   GtkTextIter start, end;
1393   if (gtk_text_buffer_get_selection_bounds (_buffer, &start, &end))
1394   {
1395     // replace the range with a rot13'ed copy.
1396     gchar* pch = gtk_text_buffer_get_text (_buffer, &start, &end, false);
1397     const size_t len = strlen (pch);
1398     TextMassager :: rot13_inplace (pch);
1399     gtk_text_buffer_delete (_buffer, &start, &end);
1400     gtk_text_buffer_insert (_buffer, &end, pch, len);
1401     g_free (pch);
1402 
1403     // resync selection.
1404     // since gtk_text_buffer_insert() invalided start, we rebuild it first.
1405     start = end;
1406     gtk_text_iter_backward_chars (&start, len);
1407     gtk_text_buffer_select_range (_buffer, &start, &end);
1408   }
1409 }
1410 
1411 /***
1412 ****
1413 ***/
1414 
1415 gboolean
expander_activated_idle(gpointer self_gpointer)1416 BodyPane :: expander_activated_idle (gpointer self_gpointer)
1417 {
1418   BodyPane *  self (static_cast<BodyPane*>(self_gpointer));
1419   GtkExpander * ex (GTK_EXPANDER(self->_expander));
1420   const bool expanded = gtk_expander_get_expanded (ex);
1421   gtk_expander_set_label_widget (ex, expanded ? self->_verbose : self->_terse);
1422   self->_prefs.set_flag ("body-pane-headers-expanded", expanded);
1423   return false;
1424 }
1425 
1426 void
expander_activated_cb(GtkExpander *,gpointer self_gpointer)1427 BodyPane :: expander_activated_cb (GtkExpander*, gpointer self_gpointer)
1428 {
1429   g_idle_add (expander_activated_idle, self_gpointer);
1430 }
1431 
1432 void
verbose_clicked_cb(GtkWidget * w,GdkEvent * event,gpointer self_gpointer)1433 BodyPane :: verbose_clicked_cb (GtkWidget* w, GdkEvent  *event, gpointer self_gpointer)
1434 {
1435   BodyPane *  self (static_cast<BodyPane*>(self_gpointer));
1436   GtkExpander * ex (GTK_EXPANDER(self->_expander));
1437   gtk_expander_set_expanded (ex, !gtk_expander_get_expanded (ex));
1438   g_idle_add (expander_activated_idle, self_gpointer);
1439 }
1440 
1441 void
refresh_scroll_visible_state()1442 BodyPane :: refresh_scroll_visible_state ()
1443 {
1444   GtkScrolledWindow * w (GTK_SCROLLED_WINDOW (_scroll));
1445   GtkAdjustment * adj = gtk_scrolled_window_get_hadjustment (w);
1446   _hscroll_visible = gtk_adjustment_get_page_size(adj) < gtk_adjustment_get_upper(adj);
1447   adj = gtk_scrolled_window_get_vadjustment (w);
1448   _vscroll_visible = gtk_adjustment_get_page_size(adj) < gtk_adjustment_get_upper(adj);
1449 }
1450 
1451 // show*_cb exists to ensure that _hscroll_visible and _vscroll_visible
1452 // are initialized properly when the body pane becomes shown onscreen.
1453 
1454 gboolean
show_idle_cb(gpointer pane)1455 BodyPane :: show_idle_cb (gpointer pane)
1456 {
1457   static_cast<BodyPane*>(pane)->refresh_scroll_visible_state ();
1458   return false;
1459 }
1460 void
show_cb(GtkWidget * w G_GNUC_UNUSED,gpointer pane)1461 BodyPane :: show_cb (GtkWidget* w G_GNUC_UNUSED, gpointer pane)
1462 {
1463   g_idle_add (show_idle_cb, pane);
1464 }
1465 
1466 namespace
1467 {
1468   guint text_size_allocated_idle_tag (0);
1469 }
1470 
1471 gboolean
text_size_allocated_idle_cb(gpointer pane)1472 BodyPane :: text_size_allocated_idle_cb (gpointer pane)
1473 {
1474   static_cast<BodyPane*>(pane)->text_size_allocated_idle ();
1475   text_size_allocated_idle_tag = 0;
1476   return false;
1477 }
1478 
1479 void
text_size_allocated_idle()1480 BodyPane :: text_size_allocated_idle ()
1481 {
1482 
1483   // prevent oscillation
1484   const bool old_h (_hscroll_visible);
1485   const bool old_v (_vscroll_visible);
1486   refresh_scroll_visible_state ();
1487   if ((old_h!=_hscroll_visible) || (old_v!=_vscroll_visible))
1488     return;
1489 
1490   // get the resize flag...
1491   GtkTextBuffer * buf (gtk_text_view_get_buffer (GTK_TEXT_VIEW(_text)));
1492   const bool fullsize (get_fullsize_flag (buf));
1493 
1494   // get the start point...
1495   GtkTextIter iter;
1496   gtk_text_buffer_get_start_iter (buf, &iter);
1497 
1498   // walk through the buffer looking for pictures to resize
1499   GtkTextTag * tag (get_named_tag_from_view (_text, "pixbuf"));
1500   GtkAllocation aloc;
1501   for (;;) {
1502     if (gtk_text_iter_begins_tag (&iter, tag))
1503     {
1504       gtk_widget_get_allocation(_text, &aloc);
1505 //      std::cerr<<"1449 alloc "<<aloc.x<<" "<<aloc.y<<" "<<aloc.width<<" "<<aloc.height<<"\n";
1506       resize_picture_at_iter (buf, &iter, fullsize, &aloc, tag);
1507     }
1508     if (!gtk_text_iter_forward_char (&iter))
1509       break;
1510     if (!gtk_text_iter_forward_to_tag_toggle (&iter, tag))
1511       break;
1512   }
1513 }
1514 
1515 void
text_size_allocated(GtkWidget * text G_GNUC_UNUSED,GtkAllocation * allocation G_GNUC_UNUSED,gpointer pane)1516 BodyPane :: text_size_allocated (GtkWidget     * text        G_GNUC_UNUSED,
1517                                  GtkAllocation * allocation  G_GNUC_UNUSED,
1518                                  gpointer        pane)
1519 {
1520   if (!text_size_allocated_idle_tag)
1521        text_size_allocated_idle_tag = g_idle_add (text_size_allocated_idle_cb, pane);
1522 }
1523 
1524 /***
1525 ****
1526 ***/
1527 
1528 void
copy_url_cb(GtkMenuItem * mi G_GNUC_UNUSED,gpointer pane)1529 BodyPane :: copy_url_cb (GtkMenuItem *mi G_GNUC_UNUSED, gpointer pane)
1530 {
1531   static_cast<BodyPane*>(pane)->copy_url ();
1532 }
1533 void
copy_url()1534 BodyPane :: copy_url ()
1535 {
1536   gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
1537                           _hover_url.c_str(), _hover_url.size());
1538 }
1539 
1540 
1541 void
populate_popup_cb(GtkTextView * v,GtkMenu * m,gpointer pane)1542 BodyPane :: populate_popup_cb (GtkTextView *v, GtkMenu *m, gpointer pane)
1543 {
1544   static_cast<BodyPane*>(pane)->populate_popup(v, m);
1545 }
1546 
1547 void
populate_popup(GtkTextView * v G_GNUC_UNUSED,GtkMenu * m)1548 BodyPane :: populate_popup (GtkTextView *v G_GNUC_UNUSED, GtkMenu *m)
1549 {
1550   // menu separator comes first.
1551   GtkWidget * mi = gtk_menu_item_new();
1552   gtk_widget_show (mi);
1553   gtk_menu_shell_prepend (GTK_MENU_SHELL(m), mi);
1554 
1555   // then, on top of it, the suggestions menu.
1556   const bool copy_url_enabled = !_hover_url.empty();
1557   GtkWidget * img = gtk_image_new_from_stock (GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
1558   mi = gtk_image_menu_item_new_with_mnemonic (_("Copy _URL"));
1559   g_signal_connect (mi, "activate", G_CALLBACK(copy_url_cb), this);
1560   gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
1561   gtk_widget_set_sensitive (mi, copy_url_enabled);
1562   gtk_widget_show_all (mi);
1563   gtk_menu_shell_prepend (GTK_MENU_SHELL(m), mi);
1564 }
1565 
1566 namespace
1567 {
1568   static gboolean
attachment_clicked_cb(GtkWidget * w,GdkEventButton * event,gpointer p)1569   attachment_clicked_cb (GtkWidget *w, GdkEventButton *event, gpointer p)
1570   {
1571     BodyPane * bp = static_cast<BodyPane*>(p);
1572     const gchar * fn = (char*)g_object_get_data (G_OBJECT(w), "filename");
1573     if (!fn) return TRUE;
1574 
1575     bp->_current_attachment = fn;
1576 
1577     if (event->type == GDK_BUTTON_PRESS && event->button == 3)
1578     {
1579       gtk_menu_popup
1580       (GTK_MENU(bp->_menu), NULL, NULL, NULL, NULL,
1581       (event ? event->button : 0),
1582       (event ? event->time : 0));
1583     }
1584 
1585     return TRUE;
1586   }
1587 }
1588 
1589 void
menu_clicked(const MenuSelection & ms)1590 BodyPane :: menu_clicked (const MenuSelection& ms)
1591 {
1592   std::vector<Article> copies;
1593   copies.push_back(_article);
1594   GroupPrefs _group_prefs;
1595   SaveAttachmentsDialog * dialog = new SaveAttachmentsDialog (
1596     _prefs, _group_prefs, _data, _data, _cache,
1597     _data, _queue, get_window(_root),
1598     _header_pane->get_group(), copies,
1599     ms == MENU_SAVE_ALL ? TaskArticle::SAVE_ALL : TaskArticle::SAVE_AS,
1600     _current_attachment);
1601   gtk_widget_show (dialog->root());
1602 }
1603 
1604 void
menu_clicked_as_cb(GtkWidget * w,gpointer ptr)1605 BodyPane :: menu_clicked_as_cb (GtkWidget* w, gpointer ptr)
1606 {
1607   BodyPane* p = static_cast<BodyPane*>(ptr);
1608   if (!p) return;
1609   p->menu_clicked(MENU_SAVE_AS);
1610 }
1611 
1612 void
menu_clicked_all_cb(GtkWidget * w,gpointer ptr)1613 BodyPane :: menu_clicked_all_cb (GtkWidget* w, gpointer ptr)
1614 {
1615   BodyPane* p = static_cast<BodyPane*>(ptr);
1616   if (!p) return;
1617   p->menu_clicked(MENU_SAVE_ALL);
1618 }
1619 
1620 
1621 GtkWidget*
new_attachment(const char * filename)1622 BodyPane :: new_attachment (const char* filename)
1623 {
1624   if (!filename) return 0;
1625 
1626   GtkWidget* w = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1627   GtkWidget* attachment = gtk_label_new(filename);
1628   GtkWidget * image = gtk_image_new_from_stock(GTK_STOCK_FILE, GTK_ICON_SIZE_MENU);
1629 
1630   gtk_label_set_selectable (GTK_LABEL(attachment), true);
1631 
1632   GtkWidget *event_box = gtk_event_box_new ();
1633   gtk_container_add (GTK_CONTAINER (event_box), image);
1634   g_object_set_data (G_OBJECT(event_box), "filename", (gpointer)filename);
1635 
1636   gtk_box_pack_start (GTK_BOX(w), event_box, false, false, 0);
1637   gtk_box_pack_start (GTK_BOX(w), attachment, false, false, 0);
1638 
1639   g_signal_connect(event_box, "button_press_event", G_CALLBACK(attachment_clicked_cb), this);
1640 
1641   return w;
1642 }
1643 
1644 
1645 void
clear_attachments()1646 BodyPane :: clear_attachments()
1647 {
1648   _cur_col = 0;
1649   _cur_row = 0;
1650   _attachments = 0;
1651   _current_attachment = 0;
1652 
1653   {
1654     gtk_widget_set_no_show_all (_att_box, TRUE);
1655     gtk_widget_hide (_att_box);
1656     gtk_container_remove (GTK_CONTAINER (_att_box), _att_toolbar);
1657     _att_toolbar = NULL;
1658     (void)create_attachments_toolbar(_att_box);
1659   }
1660 
1661 }
1662 
1663 
1664 /// FIXME : shows only half the icon on gtk2+, gtk3+ works fine. hm....
1665 void
add_attachment_to_toolbar(const char * fn)1666 BodyPane :: add_attachment_to_toolbar (const char* fn)
1667 {
1668   if (!fn) return;
1669 
1670   GtkWidget* w = new_attachment(fn);
1671 //  gtk_widget_set_size_request(w, -1, 32);
1672 
1673   ++_attachments;
1674 #if !GTK_CHECK_VERSION(3,0,0)
1675 
1676   const guint cols(4);
1677 
1678   if (_attachments % 4 == 0 && _attachments != 0)
1679   {
1680     ++_cur_row;
1681     gtk_table_resize (GTK_TABLE(_att_toolbar), _cur_row, cols);
1682     _cur_col = 0;
1683   }
1684 
1685   gtk_table_attach_defaults (GTK_TABLE(_att_toolbar), w, _cur_col, _cur_col+1, _cur_row,_cur_row+1);
1686 
1687   ++_cur_col;
1688 #else
1689   if (_attachments % 4 == 0 && _attachments != 0)
1690   {
1691     gtk_grid_insert_row (GTK_GRID(_att_toolbar), ++_cur_row);
1692     _cur_col = 0;
1693   }
1694 
1695   gtk_grid_attach (GTK_GRID(_att_toolbar), w, _cur_col++, _cur_row, 1, 1);
1696 #endif  // 3.0.0
1697 
1698   gtk_widget_set_no_show_all (_att_box, FALSE);
1699   gtk_widget_show_all (_att_box);
1700 }
1701 
1702 #ifdef HAVE_WEBKIT
1703 void
set_html_text(const char * text)1704 BodyPane :: set_html_text (const char* text)
1705 {
1706   webkit_web_view_load_string (WEBKIT_WEB_VIEW (_web_view), text, NULL, NULL, "");
1707 }
1708 #endif
1709 
1710 GtkWidget*
create_attachments_toolbar(GtkWidget * box)1711 BodyPane :: create_attachments_toolbar (GtkWidget* box)
1712 {
1713 
1714   _cur_col = 0;
1715   _cur_row = 0;
1716 
1717   GtkWidget * w;
1718 #if !GTK_CHECK_VERSION(3,0,0)
1719   w = _att_toolbar = gtk_table_new(1,4,TRUE);
1720   gtk_table_set_col_spacings (GTK_TABLE(w), PAD);
1721   gtk_table_set_row_spacings (GTK_TABLE(w), PAD);
1722 #else
1723   w = _att_toolbar = gtk_grid_new();
1724   gtk_grid_insert_row (GTK_GRID(w), 0);
1725   gtk_grid_set_column_spacing (GTK_GRID(w), 3);
1726   gtk_grid_set_row_spacing (GTK_GRID (w), 4);
1727 #endif
1728   gtk_container_add (GTK_CONTAINER (box), w);
1729 
1730   return box;
1731 }
1732 
1733 /***
1734 ****
1735 ***/
1736 
BodyPane(Data & data,ArticleCache & cache,Prefs & prefs,GroupPrefs & gp,Queue & q,HeaderPane * hp)1737 BodyPane :: BodyPane (Data& data, ArticleCache& cache, Prefs& prefs, GroupPrefs & gp, Queue& q, HeaderPane* hp):
1738   _prefs (prefs),
1739   _group_prefs(gp),
1740   _queue(q),
1741   _header_pane(hp),
1742   _data (data),
1743   _cache (cache),
1744   _hscroll_visible (false),
1745   _vscroll_visible (false),
1746   _message (0),
1747 #ifdef HAVE_GMIME_CRYPTO
1748 //  _gpgerr(GPG_DECODE),
1749 #endif
1750   _attachments(0),
1751   _current_attachment(0),
1752   _cleared (true)
1753 #ifdef HAVE_WEBKIT
1754   ,_web_view (webkit_web_view_new ())
1755 #endif
1756 {
1757 
1758   GtkWidget * w, * l, * hbox;
1759 
1760   for (guint i=0; i<NUM_ICONS; ++i)
1761     icons[i].pixbuf = gdk_pixbuf_new_from_inline (-1, icons[i].pixbuf_txt, FALSE, 0);
1762 
1763   create_cursors();
1764   create_emoticons();
1765 
1766   // menu for popup menu for attachments
1767   _menu = gtk_menu_new ();
1768   l = gtk_menu_item_new_with_label(_("Save Attachment As…"));
1769   g_signal_connect (l, "activate", G_CALLBACK(menu_clicked_as_cb), this);
1770   gtk_menu_shell_append (GTK_MENU_SHELL(_menu), l);
1771   l =  gtk_menu_item_new_with_label(_("Save All Attachments"));
1772   _selection = MENU_SAVE_ALL;
1773   g_signal_connect (l, "activate", G_CALLBACK(menu_clicked_all_cb), this);
1774   gtk_menu_shell_append (GTK_MENU_SHELL(_menu),l);
1775   gtk_widget_show_all(_menu);
1776 
1777   GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1778   gtk_container_set_resize_mode (GTK_CONTAINER(vbox), GTK_RESIZE_QUEUE);
1779 
1780   // about this expander... getting the ellipsis to work is a strange process.
1781   // once you turn ellipsize on, the expander tries to make its label as narrow
1782   // as it can and just have the three "..."s.  So gtk_label_set_width_chars
1783   // is used to force labels to want to be the right size... but then they
1784   // never ellipsize.  But, if we start with gtk_widget_set_size_request() to
1785   // tell the expander that it _wants_ to be very small, then it will still take
1786   // extra space given to it by its parent without asking for enough size to
1787   // fit the entire label.
1788   w = _expander = gtk_expander_new (NULL);
1789   gtk_widget_set_size_request (w, 50, -1);
1790   g_signal_connect (w, "activate", G_CALLBACK(expander_activated_cb), this);
1791   gtk_box_pack_start (GTK_BOX(vbox), w, false, false, 0);
1792 #if !GTK_CHECK_VERSION(3,0,0)
1793   gtk_box_pack_start (GTK_BOX(vbox), gtk_hseparator_new(), false, false, 0);
1794 #else
1795   gtk_box_pack_start (GTK_BOX(vbox), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), false, false, 0);
1796 #endif
1797 
1798   _terse = gtk_label_new ("Expander");
1799   g_object_ref_sink (G_OBJECT(_terse));
1800   gtk_misc_set_alignment (GTK_MISC(_terse), 0.0f, 0.5f);
1801   gtk_label_set_use_markup (GTK_LABEL(_terse), true);
1802   gtk_label_set_selectable (GTK_LABEL(_terse), true);
1803   gtk_label_set_ellipsize (GTK_LABEL(_terse), PANGO_ELLIPSIZE_MIDDLE);
1804   gtk_widget_show (_terse);
1805   g_signal_connect (_terse, "button-press-event", G_CALLBACK(verbose_clicked_cb), this);
1806 
1807   hbox = _verbose = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1808   g_object_ref_sink (G_OBJECT(_verbose));
1809   w = _headers = gtk_label_new ("Headers");
1810   gtk_label_set_selectable (GTK_LABEL(_headers), TRUE);
1811   gtk_misc_set_alignment (GTK_MISC(w), 0.0f, 0.5f);
1812   gtk_label_set_ellipsize (GTK_LABEL(w), PANGO_ELLIPSIZE_MIDDLE);
1813   gtk_label_set_use_markup (GTK_LABEL(w), true);
1814   gtk_box_pack_start (GTK_BOX(hbox), w, true, true, PAD_SMALL);
1815 
1816   w = _xface = gtk_image_new();
1817   gtk_widget_set_size_request (w, 0, FACE_SIZE);
1818   gtk_box_pack_start (GTK_BOX(hbox), w, false, false, PAD_SMALL);
1819   w = _face = gtk_image_new ();
1820   gtk_widget_set_size_request (w, 0, FACE_SIZE);
1821   gtk_box_pack_start (GTK_BOX(hbox), w, false, false, PAD_SMALL);
1822   gtk_widget_show_all (_verbose);
1823   g_signal_connect (_verbose, "button-press-event", G_CALLBACK(verbose_clicked_cb), this);
1824 #ifdef HAVE_GMIME_CRYPTO
1825   gtk_widget_set_has_tooltip (_verbose, true);
1826   g_signal_connect(_verbose,"query-tooltip",G_CALLBACK(on_verbose_tooltip_cb), this);
1827 #endif
1828 
1829   // setup
1830   _text = gtk_text_view_new ();
1831   refresh_fonts ();
1832   gtk_widget_add_events (_text, GDK_POINTER_MOTION_MASK|GDK_POINTER_MOTION_HINT_MASK);
1833   gtk_container_set_border_width (GTK_CONTAINER(_text), PAD_SMALL);
1834   gtk_text_view_set_editable (GTK_TEXT_VIEW(_text), false);
1835   gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW(_text), false);
1836   _scroll = gtk_scrolled_window_new (NULL, NULL);
1837   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (_scroll),
1838                                   GTK_POLICY_AUTOMATIC,
1839                                   GTK_POLICY_AUTOMATIC);
1840   gtk_container_add (GTK_CONTAINER(_scroll), _text);
1841   gtk_widget_show_all (vbox);
1842   gtk_box_pack_start (GTK_BOX(vbox), _scroll, true, true, 0);
1843 
1844   // add a toolbar for attachments
1845   _att_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1846   GtkWidget * att_label = gtk_label_new (_("Attachments:"));
1847   gtk_misc_set_padding (GTK_MISC(att_label), PAD_SMALL, 0);
1848   gtk_misc_set_alignment (GTK_MISC(att_label), 0, 0);
1849 #if !GTK_CHECK_VERSION(3,0,0)
1850   gtk_box_pack_start (GTK_BOX(_att_box), gtk_hseparator_new(), false, false, 0);
1851 #else
1852   gtk_box_pack_start (GTK_BOX(_att_box), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), false, false, 0);
1853 #endif
1854   gtk_box_pack_start (GTK_BOX(_att_box), att_label, false, false, 0);
1855   gtk_box_pack_start (GTK_BOX(vbox), create_attachments_toolbar (_att_box), false, false, 0);
1856 #if !GTK_CHECK_VERSION(3,0,0)
1857   gtk_box_pack_start (GTK_BOX(vbox), gtk_hseparator_new(), false, false, 0);
1858 #else
1859   gtk_box_pack_start (GTK_BOX(vbox), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), false, false, 0);
1860 #endif
1861 
1862   // set up the buffer tags
1863   _buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(_text));
1864   set_text_buffer_tags (_buffer, _prefs);
1865 
1866   set_text_from_message (0);
1867   const bool expanded (_prefs.get_flag ("body-pane-headers-expanded", true));
1868   gtk_expander_set_expanded (GTK_EXPANDER(_expander), expanded);
1869   expander_activated_idle (this);
1870 
1871 #ifdef HAVE_WEBKIT
1872   w = gtk_notebook_new ();
1873   GtkNotebook * n (GTK_NOTEBOOK (w));
1874   gtk_notebook_append_page (n, vbox, gtk_label_new (_("Text View")));
1875 
1876   // add scroll to html
1877   GtkWidget* scroll = gtk_scrolled_window_new (NULL, NULL);
1878   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
1879   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
1880                                   GTK_POLICY_AUTOMATIC,
1881                                   GTK_POLICY_AUTOMATIC);
1882   gtk_container_add (GTK_CONTAINER(scroll), _web_view);
1883   gtk_notebook_append_page (n, scroll, gtk_label_new (_("HTML View")));
1884 #else
1885   w = vbox;
1886 #endif
1887   _root = w;
1888   _prefs.add_listener (this);
1889 
1890   // listen for user interaction
1891   g_signal_connect (_text, "motion_notify_event", G_CALLBACK(motion_notify_event), &_hover_url);
1892   g_signal_connect (_text, "button_press_event", G_CALLBACK(mouse_button_pressed_cb), this);
1893   g_signal_connect (_text, "key_press_event", G_CALLBACK(text_key_pressed), _scroll);
1894   g_signal_connect (_text, "size_allocate", G_CALLBACK(text_size_allocated), this);
1895   g_signal_connect (_text, "populate_popup", G_CALLBACK(populate_popup_cb), this);
1896   g_signal_connect (_root, "show", G_CALLBACK(show_cb), this);
1897 
1898   gtk_widget_show_all (_root);
1899 }
1900 
~BodyPane()1901 BodyPane :: ~BodyPane ()
1902 {
1903   _prefs.remove_listener (this);
1904 
1905   g_object_unref (_verbose);
1906   g_object_unref (_terse);
1907 
1908   if (_message)
1909     g_object_unref (_message);
1910 
1911   for (int i=0; i<NUM_ICONS; ++i)
1912     g_object_unref (icons[i].pixbuf);
1913 
1914   foreach (std::set<char*>, _attach_names, it)
1915     g_free(*it);
1916 
1917   // store last opened message in prefs
1918   _prefs.set_string("last-opened-msg", get_cleared() ? "" : get_message_id().to_view());
1919   free_emoticons();
1920   free_cursors();
1921 }
1922 
1923 
1924 namespace
1925 {
1926   const int smooth_scrolling_speed (10);
1927 
sylpheed_textview_smooth_scroll_do(GtkAdjustment * vadj,gfloat old_value,gfloat new_value,int step)1928   void sylpheed_textview_smooth_scroll_do (GtkAdjustment  * vadj,
1929                                            gfloat           old_value,
1930                                            gfloat           new_value,
1931                                            int              step)
1932   {
1933     const bool down (old_value < new_value);
1934     const int change_value = (int)(down ? new_value-old_value : old_value-new_value);
1935     for (int i=step; i<=change_value; i+=step)
1936       gtk_adjustment_set_value (vadj, old_value+(down?i:-i));
1937     gtk_adjustment_set_value (vadj, new_value);
1938   }
1939 }
1940 
1941 bool
read_more_or_less(bool more)1942 BodyPane :: read_more_or_less (bool more)
1943 {
1944   GtkWidget * parent = gtk_widget_get_parent (_text);
1945   GtkAdjustment * v = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW(parent));
1946 
1947   // figure out how far we scroll
1948   const double v_value = gtk_adjustment_get_value(v);
1949   const double v_upper = gtk_adjustment_get_upper(v);
1950   const double v_lower = gtk_adjustment_get_lower(v);
1951   const double v_page_size = gtk_adjustment_get_page_size(v);
1952   const int arbitrary_font_height_pixels_hack (18);
1953   const float inc (v_page_size - arbitrary_font_height_pixels_hack);
1954   const gfloat val (CLAMP (v_value + (more ? inc : -inc),
1955                            v_lower,
1956                            MAX(v_upper,v_page_size)-MIN(v_upper,v_page_size)));
1957 
1958   // if we can scroll, do so.
1959   bool handled (false);
1960   if (v_upper>=v_page_size && val!=v_value)
1961   {
1962     if (_prefs.get_flag ("smooth-scrolling", true))
1963       sylpheed_textview_smooth_scroll_do (v, v_value, val, smooth_scrolling_speed);
1964     else
1965       gtk_adjustment_set_value (v, val);
1966 
1967     handled = true;
1968   }
1969 
1970   return handled;
1971 }
1972 
1973 namespace
1974 {
1975   // (1) strip redundant leading Re: and RE:
1976   // (2) ensure the remaining Re: has a lowercase e
1977 
normalize_subject_re(const StringView & v_in)1978   std::string normalize_subject_re (const StringView& v_in)
1979   {
1980     StringView v(v_in), prev(v_in);
1981     while (!v.empty()) {
1982       v.ltrim ();
1983       StringView tmp (v);
1984       if (tmp.strstr("Re:") == tmp.str)
1985         tmp.eat_chars (3);
1986       else if (v.strstr("RE:") == tmp.str)
1987         tmp.eat_chars (3);
1988       else
1989         break;
1990       prev = v;
1991       v = tmp;
1992     }
1993 
1994     std::string ret (prev.str, prev.len);
1995     if (!ret.find("RE:")) // force lowercase 'e'
1996       ret.replace (0, 3, "Re:");
1997 
1998     return ret;
1999   }
2000 
get_header(GMimeMessage * msg,const char * key,const char * fallback_charset_1,const char * fallback_charset_2)2001   std::string get_header (GMimeMessage * msg,
2002                           const char   * key,
2003                           const char   * fallback_charset_1,
2004                           const char   * fallback_charset_2)
2005   {
2006     const StringView v (g_mime_object_get_header ((GMimeObject *) msg, key));
2007     std::string s;
2008     if (!v.empty())
2009       s = header_to_utf8 (v, fallback_charset_1, fallback_charset_2);
2010     return s;
2011   }
2012 
2013   struct ForeachPartData
2014   {
2015     std::string fallback_charset;
2016     std::string body;
2017   };
2018 
get_utf8_body_foreach_part(GMimeObject *,GMimeObject * o,gpointer user_data)2019   void get_utf8_body_foreach_part (GMimeObject* /*parent*/, GMimeObject *o,
2020                                    gpointer user_data)
2021   {
2022     GMimePart * part;
2023     GMimeContentType * type = g_mime_object_get_content_type (o);
2024     const bool is_text (g_mime_content_type_is_type (type, "text", "*"));
2025     if (is_text)
2026     {
2027       part = GMIME_PART (o);
2028       ForeachPartData *data (static_cast<ForeachPartData*>(user_data));
2029       data->body += mime_part_to_utf8 (part, data->fallback_charset.c_str());
2030     }
2031   }
2032 
get_utf8_body(GMimeMessage * source,const char * fallback_charset)2033   std::string get_utf8_body (GMimeMessage * source,
2034                              const char   * fallback_charset)
2035   {
2036     ForeachPartData tmp;
2037     if (fallback_charset)
2038       tmp.fallback_charset = fallback_charset;
2039     if (source)
2040       g_mime_message_foreach (source, get_utf8_body_foreach_part, &tmp);
2041     return tmp.body;
2042   }
2043 }
2044 
2045 GMimeMessage*
create_followup_or_reply(bool is_reply)2046 BodyPane :: create_followup_or_reply (bool is_reply)
2047 {
2048   GMimeMessage * msg (0);
2049 
2050   if (_message)
2051   {
2052     msg = g_mime_message_new (false);
2053     GMimeObject *msg_obj = (GMimeObject*)msg;
2054     GMimeObject *_message_obj = (GMimeObject*)_message;
2055 
2056     // fallback character encodings
2057     const char * group_charset (_charset.c_str());
2058     GMimeContentType * type (g_mime_object_get_content_type (GMIME_OBJECT(_message)));
2059     const char * message_charset (type ? g_mime_content_type_get_parameter (type, "charset") : 0);
2060 
2061     ///
2062     ///  HEADERS
2063     ///
2064 
2065     // To:, Newsgroups:
2066     const std::string from       (get_header (_message, "From",        message_charset, group_charset));
2067     const std::string newsgroups (get_header (_message, "Newsgroups",  message_charset, group_charset));
2068     const std::string fup_to     (get_header (_message, "Followup-To", message_charset, group_charset));
2069     const std::string reply_to   (get_header (_message, "Reply-To",    message_charset, group_charset));
2070 
2071     if (is_reply || fup_to=="poster") {
2072       const std::string& to (reply_to.empty() ? from : reply_to);
2073 #ifdef HAVE_GMIME_30
2074       pan_g_mime_message_add_recipients_from_string (msg, GMIME_ADDRESS_TYPE_TO, to.c_str());
2075 #else
2076       pan_g_mime_message_add_recipients_from_string (msg, GMIME_RECIPIENT_TYPE_TO, to.c_str());
2077 #endif
2078     } else {
2079       const std::string& groups (fup_to.empty() ? newsgroups : fup_to);
2080 #ifdef HAVE_GMIME_30
2081       g_mime_object_append_header ((GMimeObject *) msg, "Newsgroups", groups.c_str(), NULL);
2082 #else
2083       g_mime_object_append_header ((GMimeObject *) msg, "Newsgroups", groups.c_str());
2084 #endif
2085     }
2086 
2087     // Subject:
2088     StringView v = g_mime_object_get_header (_message_obj, "Subject");
2089     std::string h = header_to_utf8 (v, message_charset, group_charset);
2090     std::string val (normalize_subject_re (h));
2091     if (val.find ("Re:") != 0) // add "Re: " if we don't have one
2092       val.insert (0, "Re: ");
2093 #ifdef HAVE_GMIME_30
2094     g_mime_message_set_subject (msg, val.c_str(), NULL);
2095 #else
2096     g_mime_message_set_subject (msg, val.c_str());
2097 #endif
2098 
2099     // attribution lines
2100     const char * cpch = g_mime_object_get_header (_message_obj, "From");
2101     h = header_to_utf8 (cpch, message_charset, group_charset);
2102 #ifdef HAVE_GMIME_30
2103     g_mime_object_append_header (msg_obj, "X-Draft-Attribution-Author", h.c_str(), NULL);
2104 #else
2105     g_mime_object_append_header (msg_obj, "X-Draft-Attribution-Author", h.c_str());
2106 #endif
2107 
2108     cpch = g_mime_message_get_message_id (_message);
2109     h = header_to_utf8 (cpch, message_charset, group_charset);
2110 #ifdef HAVE_GMIME_30
2111     g_mime_object_append_header (msg_obj, "X-Draft-Attribution-Id", h.c_str(), NULL);
2112 #else
2113     g_mime_object_append_header (msg_obj, "X-Draft-Attribution-Id", h.c_str());
2114 #endif
2115 
2116 #ifdef HAVE_GMIME_30
2117     const char * header_t = "Date";
2118     const char * tmp_s = g_mime_object_get_header (_message_obj, header_t);
2119     const char * tmp = tmp_s;  // FIXME: convert time to string
2120 #else
2121     char * tmp = g_mime_message_get_date_as_string (_message);
2122 #endif
2123     h = header_to_utf8 (tmp, message_charset, group_charset);
2124 #ifdef HAVE_GMIME_30
2125     g_mime_object_append_header (msg_obj, "X-Draft-Attribution-Date", h.c_str(), NULL);
2126 #else
2127     g_mime_object_append_header (msg_obj, "X-Draft-Attribution-Date", h.c_str());
2128     g_free (tmp);
2129 #endif
2130 
2131     // references
2132     const char * header = "References";
2133     v = g_mime_object_get_header (_message_obj, header);
2134     val.assign (v.str, v.len);
2135     if (!val.empty())
2136       val += ' ';
2137     val += "<";
2138     val += g_mime_message_get_message_id (_message);
2139     val += ">";
2140     val = GNKSA :: trim_references (val);
2141 #ifdef HAVE_GMIME_30
2142     g_mime_object_append_header (msg_obj, header, val.c_str(), NULL);
2143 #else
2144     g_mime_object_append_header (msg_obj, header, val.c_str());
2145 #endif
2146 
2147     ///
2148     ///  BODY
2149     ///
2150 
2151     GtkTextIter start, end;
2152     if (gtk_text_buffer_get_selection_bounds (_buffer, &start, &end))
2153     {
2154       // go with the user-selected region w/o modifications.
2155       // since it's in the text pane it's already utf-8...
2156       h = gtk_text_buffer_get_text (_buffer, &start, &end, false);
2157     }
2158     else
2159     {
2160       // get the body; remove the sig
2161       h = get_utf8_body (_message, group_charset);
2162       StringView v (h);
2163       int sig_index (0);
2164       if (GNKSA::find_signature_delimiter (h, sig_index) != GNKSA::SIG_NONE)
2165         v.len = sig_index;
2166       v.trim ();
2167       h = std::string (v.str, v.len);
2168     }
2169 
2170     // quote the body
2171     std::string s;
2172     for (const char *c(h.c_str()); c && *c; ++c) {
2173       if (c==h.c_str() || c[-1]=='\n')
2174         s += (*c=='>' ? ">" : "> ");
2175       s += *c;
2176     }
2177 
2178     // set the clone's content object with our modified body
2179     GMimeStream * stream = g_mime_stream_mem_new ();
2180     g_mime_stream_write_string (stream, s.c_str());
2181     GMimeDataWrapper * wrapper = g_mime_data_wrapper_new_with_stream (stream, GMIME_CONTENT_ENCODING_8BIT);
2182     GMimePart * part = g_mime_part_new ();
2183 #ifdef HAVE_GMIME_30
2184     GMimeContentType * new_type = g_mime_content_type_parse (NULL, "text/plain; charset=UTF-8");
2185 #else
2186     GMimeContentType * new_type = g_mime_content_type_new_from_string ("text/plain; charset=UTF-8");
2187 #endif
2188     g_mime_object_set_content_type ((GMimeObject *) part, new_type);
2189 #ifdef HAVE_GMIME_30
2190     g_mime_part_set_content (part, wrapper);
2191 #else
2192     g_mime_part_set_content_object (part, wrapper);
2193 #endif
2194     g_mime_part_set_content_encoding (part, GMIME_CONTENT_ENCODING_8BIT);
2195     g_mime_message_set_mime_part (msg, GMIME_OBJECT(part));
2196     g_object_unref (new_type);
2197     g_object_unref (wrapper);
2198     g_object_unref (part);
2199     g_object_unref (stream);
2200 
2201   //std::cerr << LINE_ID << " here is the modified clone\n [" << g_mime_object_to_string((GMimeObject *)msg) << ']' << std::endl;
2202 
2203   }
2204 
2205   return msg;
2206 }
2207 
2208 /***
2209 ****
2210 ***/
2211 
2212 void
refresh_fonts()2213 BodyPane :: refresh_fonts ()
2214 {
2215   const bool body_pane_font_enabled = _prefs.get_flag ("body-pane-font-enabled", false);
2216   const bool monospace_font_enabled = _prefs.get_flag ("monospace-font-enabled", false);
2217 
2218   if (!body_pane_font_enabled && !monospace_font_enabled)
2219     gtk_widget_override_font (_text, 0);
2220   else {
2221     const std::string str (monospace_font_enabled
2222       ? _prefs.get_string ("monospace-font", "Monospace 10")
2223       : _prefs.get_string ("body-pane-font", "Sans 10"));
2224     PangoFontDescription * pfd (pango_font_description_from_string (str.c_str()));
2225     gtk_widget_override_font (_text, pfd);
2226     pango_font_description_free (pfd);
2227   }
2228 }
2229 
2230 void
on_prefs_flag_changed(const StringView & key,bool value G_GNUC_UNUSED)2231 BodyPane :: on_prefs_flag_changed (const StringView& key, bool value G_GNUC_UNUSED)
2232 {
2233   if ((key=="body-pane-font-enabled") || (key=="monospace-font-enabled"))
2234     refresh_fonts ();
2235 
2236   if ((key=="wrap-article-body") || (key=="mute-quoted-text") ||
2237       (key=="mute-signature") ||
2238       (key=="show-smilies-as-graphics") || (key=="show-all-headers") ||
2239       (key=="size-pictures-to-fit") || (key=="show-text-markup") ||
2240       (key=="highlight-urls") ||
2241       (key=="show-article-sig")
2242      )
2243     refresh ();
2244 }
2245 
2246 void
on_prefs_string_changed(const StringView & key,const StringView & value G_GNUC_UNUSED)2247 BodyPane :: on_prefs_string_changed (const StringView& key, const StringView& value G_GNUC_UNUSED)
2248 {
2249   if ((key=="body-pane-font") || (key=="monospace-font"))
2250     refresh_fonts ();
2251 
2252 }
2253 
2254 void
on_prefs_color_changed(const StringView & key,const GdkColor & color G_GNUC_UNUSED)2255 BodyPane :: on_prefs_color_changed (const StringView& key, const GdkColor& color G_GNUC_UNUSED)
2256 {
2257   if ((key == "text-color-fg")              || (key == "text-color-bg")              ||
2258       (key == "body-pane-color-url")        || (key == "body-pane-color-url-bg")     ||
2259       (key == "body-pane-color-quote-1")    || (key == "body-pane-color-quote-2")    ||
2260       (key == "body-pane-color-quote-3")    || (key == "body-pane-color-quote-1-bg") ||
2261       (key == "body-pane-color-quote-2-bg") || (key == "body-pane-color-quote-3-bg") ||
2262       (key == "body-pane-color-signature")  || (key == "body-pane-color-signature-bg"))
2263   {
2264     refresh_colors ();
2265   }
2266 }
2267 
2268 void
refresh_colors()2269 BodyPane :: refresh_colors ()
2270 {
2271   set_text_buffer_tags (_buffer, _prefs);
2272   set_text_from_message (_message);
2273 }
2274 
2275 void
set_character_encoding(const char * charset)2276 BodyPane :: set_character_encoding (const char * charset)
2277 {
2278   if (charset && *charset)
2279     _charset = charset;
2280   else
2281     _charset.clear ();
2282 
2283   refresh ();
2284 }
2285