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