1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2 of the License, or (at your option) version 3.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with the program; if not, see <http://www.gnu.org/licenses/>
14 *
15 *
16 * Authors:
17 * Chris Toshok <toshok@ximian.com>
18 *
19 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20 *
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include "eab-contact-display.h"
28 #include "eab-contact-formatter.h"
29
30 #include "eab-gui-util.h"
31 #include "e-util/e-util.h"
32 #include "e-util/e-util-private.h"
33 #include "e-util/e-html-utils.h"
34 #include "e-util/e-icon-factory.h"
35 #include "e-util/e-plugin-ui.h"
36 #include "e-util/e-file-request.h"
37 #include "e-util/e-stock-request.h"
38
39 #include <webkit/webkit.h>
40
41 #ifdef WITH_CONTACT_MAPS
42 #include "widgets/misc/e-contact-map.h"
43 #endif
44
45 #include <string.h>
46 #include <glib/gi18n.h>
47
48 #define EAB_CONTACT_DISPLAY_GET_PRIVATE(obj) \
49 (G_TYPE_INSTANCE_GET_PRIVATE \
50 ((obj), EAB_TYPE_CONTACT_DISPLAY, EABContactDisplayPrivate))
51
52 #define TEXT_IS_RIGHT_TO_LEFT \
53 (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL)
54
55 struct _EABContactDisplayPrivate {
56 EContact *contact;
57
58 EABContactDisplayMode mode;
59 gboolean show_maps;
60
61 GCancellable *formatter_cancellable;
62 };
63
64 enum {
65 PROP_0,
66 PROP_CONTACT,
67 PROP_MODE,
68 PROP_SHOW_MAPS
69 };
70
71 enum {
72 SEND_MESSAGE,
73 LAST_SIGNAL
74 };
75
76 static const gchar *ui =
77 "<ui>"
78 " <popup name='context'>"
79 " <placeholder name='custom-actions-1'>"
80 " <menuitem action='contact-send-message'/>"
81 " </placeholder>"
82 " <placeholder name='custom-actions-2'>"
83 " <menuitem action='contact-mailto-copy'/>"
84 " </placeholder>"
85 " </popup>"
86 "</ui>";
87
88 static guint signals[LAST_SIGNAL];
89
90 G_DEFINE_TYPE (
91 EABContactDisplay,
92 eab_contact_display,
93 E_TYPE_WEB_VIEW)
94
95 static void
96 contact_display_emit_send_message (EABContactDisplay *display,
97 gint email_num)
98 {
99 EDestination *destination;
100 EContact *contact;
101
102 g_return_if_fail (email_num >= 0);
103
104 destination = e_destination_new ();
105 contact = eab_contact_display_get_contact (display);
106 e_destination_set_contact (destination, contact, email_num);
107 g_signal_emit (display, signals[SEND_MESSAGE], 0, destination);
108 g_object_unref (destination);
109 }
110
111 static void
112 action_contact_mailto_copy_cb (GtkAction *action,
113 EABContactDisplay *display)
114 {
115 GtkClipboard *clipboard;
116 EWebView *web_view;
117 EContact *contact;
118 GList *list;
119 const gchar *text;
120 const gchar *uri;
121 gint index;
122
123 web_view = E_WEB_VIEW (display);
124 uri = e_web_view_get_selected_uri (web_view);
125 g_return_if_fail (uri != NULL);
126
127 index = atoi (uri + strlen ("internal-mailto:"));
128 g_return_if_fail (index >= 0);
129
130 contact = eab_contact_display_get_contact (display);
131 list = e_contact_get (contact, E_CONTACT_EMAIL);
132 text = g_list_nth_data (list, index);
133
134 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
135 gtk_clipboard_set_text (clipboard, text, -1);
136 gtk_clipboard_store (clipboard);
137
138 g_list_foreach (list, (GFunc) g_free, NULL);
139 g_list_free (list);
140 }
141
142 static void
143 action_contact_send_message_cb (GtkAction *action,
144 EABContactDisplay *display)
145 {
146 EWebView *web_view;
147 const gchar *uri;
148 gint index;
149
150 web_view = E_WEB_VIEW (display);
151 uri = e_web_view_get_selected_uri (web_view);
152 g_return_if_fail (uri != NULL);
153
154 index = atoi (uri + strlen ("internal-mailto:"));
155 contact_display_emit_send_message (display, index);
156 }
157
158 static GtkActionEntry internal_mailto_entries[] = {
159
160 { "contact-mailto-copy",
161 GTK_STOCK_COPY,
162 N_("Copy _Email Address"),
163 NULL,
164 N_("Copy the email address to the clipboard"),
165 G_CALLBACK (action_contact_mailto_copy_cb) },
166
167 { "contact-send-message",
168 "mail-message-new",
169 N_("_Send New Message To..."),
170 NULL,
171 N_("Send a mail message to this address"),
172 G_CALLBACK (action_contact_send_message_cb) }
173 };
174
175 static void
176 contact_formatting_finished (GObject *object,
177 GSimpleAsyncResult *result,
178 gpointer user_data)
179 {
180 EABContactDisplay *display = user_data;
181 CamelStreamMem *stream;
182 gchar *html;
183 GByteArray *ba;
184
185 stream = g_simple_async_result_get_op_res_gpointer (result);
186 /* The operation was probably cancelled */
187 if (!stream)
188 return;
189
190 ba = camel_stream_mem_get_byte_array (stream);
191
192 html = g_strndup ((gchar *) ba->data, ba->len);
193 e_web_view_load_string (E_WEB_VIEW (display), html);
194
195 g_free (html);
196 g_object_unref (stream);
197 g_object_unref (object);
198 g_clear_object (&display->priv->formatter_cancellable);
199 }
200
201 static void
202 load_contact (EABContactDisplay *display)
203 {
204 EABContactFormatter *formatter;
205
206 if (display->priv->formatter_cancellable) {
207 g_cancellable_cancel (display->priv->formatter_cancellable);
208 g_clear_object (&display->priv->formatter_cancellable);
209 }
210
211 if (!display->priv->contact) {
212 e_web_view_clear (E_WEB_VIEW (display));
213 return;
214 }
215
216 formatter = eab_contact_formatter_new (
217 display->priv->mode,
218 display->priv->show_maps);
219 g_object_set (
220 G_OBJECT (formatter),
221 "style", gtk_widget_get_style (GTK_WIDGET (display)),
222 "state", gtk_widget_get_state (GTK_WIDGET (display)),
223 NULL);
224
225 display->priv->formatter_cancellable = g_cancellable_new ();
226
227 eab_contact_formatter_format_contact_async (
228 formatter, display->priv->contact,
229 display->priv->formatter_cancellable,
230 (GAsyncReadyCallback) contact_formatting_finished,
231 display);
232 }
233
234 static void
235 contact_display_set_property (GObject *object,
236 guint property_id,
237 const GValue *value,
238 GParamSpec *pspec)
239 {
240 switch (property_id) {
241 case PROP_CONTACT:
242 eab_contact_display_set_contact (
243 EAB_CONTACT_DISPLAY (object),
244 g_value_get_object (value));
245 return;
246
247 case PROP_MODE:
248 eab_contact_display_set_mode (
249 EAB_CONTACT_DISPLAY (object),
250 g_value_get_int (value));
251 return;
252
253 case PROP_SHOW_MAPS:
254 eab_contact_display_set_show_maps (
255 EAB_CONTACT_DISPLAY (object),
256 g_value_get_boolean (value));
257 return;
258 }
259
260 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
261 }
262
263 static void
264 contact_display_get_property (GObject *object,
265 guint property_id,
266 GValue *value,
267 GParamSpec *pspec)
268 {
269 switch (property_id) {
270 case PROP_CONTACT:
271 g_value_set_object (
272 value, eab_contact_display_get_contact (
273 EAB_CONTACT_DISPLAY (object)));
274 return;
275
276 case PROP_MODE:
277 g_value_set_int (
278 value, eab_contact_display_get_mode (
279 EAB_CONTACT_DISPLAY (object)));
280 return;
281
282 case PROP_SHOW_MAPS:
283 g_value_set_boolean (
284 value, eab_contact_display_get_show_maps (
285 EAB_CONTACT_DISPLAY (object)));
286 return;
287 }
288
289 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
290 }
291
292 static void
293 contact_display_dispose (GObject *object)
294 {
295 EABContactDisplayPrivate *priv;
296
297 priv = EAB_CONTACT_DISPLAY_GET_PRIVATE (object);
298
299 if (priv->contact != NULL) {
300 g_object_unref (priv->contact);
301 priv->contact = NULL;
302 }
303
304 /* Chain up to parent's dispose() method. */
305 G_OBJECT_CLASS (eab_contact_display_parent_class)->dispose (object);
306 }
307
308 static void
309 contact_display_hovering_over_link (EWebView *web_view,
310 const gchar *title,
311 const gchar *uri)
312 {
313 EWebViewClass *web_view_class;
314 EABContactDisplay *display;
315 EContact *contact;
316 const gchar *name;
317 gchar *message;
318
319 if (uri == NULL || *uri == '\0')
320 goto chainup;
321
322 if (!g_str_has_prefix (uri, "internal-mailto:"))
323 goto chainup;
324
325 display = EAB_CONTACT_DISPLAY (web_view);
326 contact = eab_contact_display_get_contact (display);
327
328 name = e_contact_get_const (contact, E_CONTACT_FILE_AS);
329 if (name == NULL)
330 e_contact_get_const (contact, E_CONTACT_FULL_NAME);
331 g_return_if_fail (name != NULL);
332
333 message = g_strdup_printf (_("Click to mail %s"), name);
334 e_web_view_status_message (web_view, message);
335 g_free (message);
336
337 return;
338
339 chainup:
340 /* Chain up to parent's hovering_over_link() method. */
341 web_view_class = E_WEB_VIEW_CLASS (eab_contact_display_parent_class);
342 web_view_class->hovering_over_link (web_view, title, uri);
343 }
344
345 static void
346 contact_display_link_clicked (EWebView *web_view,
347 const gchar *uri)
348 {
349 EABContactDisplay *display;
350 gsize length;
351
352 display = EAB_CONTACT_DISPLAY (web_view);
353
354 length = strlen ("internal-mailto:");
355 if (g_ascii_strncasecmp (uri, "internal-mailto:", length) == 0) {
356 gint index;
357
358 index = atoi (uri + length);
359 contact_display_emit_send_message (display, index);
360 return;
361 }
362
363 /* Chain up to parent's link_clicked() method. */
364 E_WEB_VIEW_CLASS (eab_contact_display_parent_class)->
365 link_clicked (web_view, uri);
366 }
367
368 #ifdef WITH_CONTACT_MAPS
369 /* XXX Clutter event handling workaround. Clutter-gtk propagates events down
370 * to parent widgets. In this case it leads to GtkHTML scrolling up and
371 * down while user's trying to zoom in the champlain widget. This
372 * workaround stops the propagation from map widget down to GtkHTML. */
373 static gboolean
374 handle_map_scroll_event (GtkWidget *widget,
375 GdkEvent *event)
376 {
377 return TRUE;
378 }
379
380 static GtkWidget *
381 contact_display_object_requested (WebKitWebView *web_view,
382 gchar *mime_type,
383 gchar *uri,
384 GHashTable *param,
385 EABContactDisplay *display)
386 {
387 EContact *contact = display->priv->contact;
388 const gchar *name = e_contact_get_const (contact, E_CONTACT_FILE_AS);
389 const gchar *contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
390 gchar *full_name;
391 EContactAddress *address;
392 GtkWidget *map = NULL;
393
394 if (strstr (mime_type, "work") != NULL) {
395 address = e_contact_get (contact, E_CONTACT_ADDRESS_WORK);
396 full_name = g_strconcat (name, " (", _("Work"), ")", NULL);
397 } else if (strstr (mime_type, "home") != NULL) {
398 address = e_contact_get (contact, E_CONTACT_ADDRESS_HOME);
399 full_name = g_strconcat (name, " (", _("Home"), ")", NULL);
400 }
401
402 if (address) {
Uninitialized variable: address
(emitted by cppcheck)
Uninitialized variable: address
(emitted by cppcheck)
403 map = e_contact_map_new ();
404 gtk_widget_set_size_request (map, 250, 250);
405 g_signal_connect (
406 E_CONTACT_MAP (map), "contact-added",
407 G_CALLBACK (e_contact_map_zoom_on_marker), NULL);
408 g_signal_connect_swapped (
409 E_CONTACT_MAP (map), "contact-added",
410 G_CALLBACK (gtk_widget_show_all), map);
411 g_signal_connect (
412 GTK_CHAMPLAIN_EMBED (map), "scroll-event",
413 G_CALLBACK (handle_map_scroll_event), NULL);
414
415 /* No need to display photo in contact preview. */
416 e_contact_map_add_marker (
417 E_CONTACT_MAP (map), full_name,
418 contact_uid, address, NULL);
419
420 gtk_widget_show_all (map);
421
422 e_contact_address_free (address);
423 }
424
425 g_free (full_name);
426
427 return map;
428 }
429 #endif
430
431 static void
432 contact_display_load_status_changed (WebKitWebView *web_view,
433 GParamSpec *pspec,
434 gpointer user_data)
435 {
436 WebKitLoadStatus load_status;
437 WebKitDOMDocument *document;
438
439 load_status = webkit_web_view_get_load_status (web_view);
440 if (load_status != WEBKIT_LOAD_FINISHED)
441 return;
442
443 document = webkit_web_view_get_dom_document (web_view);
444 eab_contact_formatter_bind_dom (document);
445 }
446
447 static void
448 contact_display_update_actions (EWebView *web_view,
449 GdkEventButton *event)
450 {
451 GtkActionGroup *action_group;
452 gboolean scheme_is_internal_mailto;
453 gboolean visible;
454 const gchar *group_name;
455 const gchar *uri;
456
457 /* Chain up to parent's update_actions() method. */
458 E_WEB_VIEW_CLASS (eab_contact_display_parent_class)->
459 update_actions (web_view, event);
460
461 uri = e_web_view_get_selected_uri (web_view);
462
463 scheme_is_internal_mailto = (uri == NULL) ? FALSE :
464 (g_ascii_strncasecmp (uri, "internal-mailto:", 16) == 0);
465
466 /* Override how EWebView treats internal-mailto URIs. */
467 group_name = "uri";
468 action_group = e_web_view_get_action_group (web_view, group_name);
469 visible = gtk_action_group_get_visible (action_group);
470 visible &= !scheme_is_internal_mailto;
471 gtk_action_group_set_visible (action_group, visible);
472
473 group_name = "internal-mailto";
474 visible = scheme_is_internal_mailto;
475 action_group = e_web_view_get_action_group (web_view, group_name);
476 gtk_action_group_set_visible (action_group, visible);
477 }
478
479 static void
480 eab_contact_display_class_init (EABContactDisplayClass *class)
481 {
482 GObjectClass *object_class;
483 EWebViewClass *web_view_class;
484
485 g_type_class_add_private (class, sizeof (EABContactDisplayPrivate));
486
487 object_class = G_OBJECT_CLASS (class);
488 object_class->set_property = contact_display_set_property;
489 object_class->get_property = contact_display_get_property;
490 object_class->dispose = contact_display_dispose;
491
492 web_view_class = E_WEB_VIEW_CLASS (class);
493 web_view_class->hovering_over_link = contact_display_hovering_over_link;
494 web_view_class->link_clicked = contact_display_link_clicked;
495 web_view_class->update_actions = contact_display_update_actions;
496
497 g_object_class_install_property (
498 object_class,
499 PROP_CONTACT,
500 g_param_spec_object (
501 "contact",
502 NULL,
503 NULL,
504 E_TYPE_CONTACT,
505 G_PARAM_READWRITE));
506
507 /* XXX Make this a real enum property. */
508 g_object_class_install_property (
509 object_class,
510 PROP_MODE,
511 g_param_spec_int (
512 "mode",
513 NULL,
514 NULL,
515 EAB_CONTACT_DISPLAY_RENDER_NORMAL,
516 EAB_CONTACT_DISPLAY_RENDER_COMPACT,
517 EAB_CONTACT_DISPLAY_RENDER_NORMAL,
518 G_PARAM_READWRITE));
519
520 g_object_class_install_property (
521 object_class,
522 PROP_SHOW_MAPS,
523 g_param_spec_boolean (
524 "show-maps",
525 NULL,
526 NULL,
527 FALSE,
528 G_PARAM_READWRITE));
529
530 signals[SEND_MESSAGE] = g_signal_new (
531 "send-message",
532 G_OBJECT_CLASS_TYPE (class),
533 G_SIGNAL_RUN_FIRST,
534 G_STRUCT_OFFSET (EABContactDisplayClass, send_message),
535 NULL, NULL,
536 g_cclosure_marshal_VOID__OBJECT,
537 G_TYPE_NONE, 1,
538 E_TYPE_DESTINATION);
539 }
540
541 static void
542 eab_contact_display_init (EABContactDisplay *display)
543 {
544 EWebView *web_view;
545 GtkUIManager *ui_manager;
546 GtkActionGroup *action_group;
547 const gchar *domain = GETTEXT_PACKAGE;
548 GError *error = NULL;
549
550 display->priv = EAB_CONTACT_DISPLAY_GET_PRIVATE (display);
551
552 web_view = E_WEB_VIEW (display);
553 ui_manager = e_web_view_get_ui_manager (web_view);
554
555 #ifdef WITH_CONTACT_MAPS
556 g_signal_connect (
557 web_view, "create-plugin-widget",
558 G_CALLBACK (contact_display_object_requested), display);
559 #endif
560 g_signal_connect (
561 web_view, "notify::load-status",
562 G_CALLBACK (contact_display_load_status_changed), NULL);
563 g_signal_connect (
564 web_view, "style-set",
565 G_CALLBACK (load_contact), NULL);
566
567 e_web_view_install_request_handler (E_WEB_VIEW (display), E_TYPE_FILE_REQUEST);
568 e_web_view_install_request_handler (E_WEB_VIEW (display), E_TYPE_STOCK_REQUEST);
569
570 action_group = gtk_action_group_new ("internal-mailto");
571 gtk_action_group_set_translation_domain (action_group, domain);
572 gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
573 g_object_unref (action_group);
574
575 gtk_action_group_add_actions (
576 action_group, internal_mailto_entries,
577 G_N_ELEMENTS (internal_mailto_entries), display);
578
579 /* Because we are loading from a hard-coded string, there is
580 * no chance of I/O errors. Failure here implies a malformed
581 * UI definition. Full stop. */
582 gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
583 if (error != NULL)
584 g_error ("%s", error->message);
585 }
586
587 GtkWidget *
588 eab_contact_display_new (void)
589 {
590 return g_object_new (EAB_TYPE_CONTACT_DISPLAY, NULL);
591 }
592
593 EContact *
594 eab_contact_display_get_contact (EABContactDisplay *display)
595 {
596 g_return_val_if_fail (EAB_IS_CONTACT_DISPLAY (display), NULL);
597
598 return display->priv->contact;
599 }
600
601 void
602 eab_contact_display_set_contact (EABContactDisplay *display,
603 EContact *contact)
604 {
605 g_return_if_fail (EAB_IS_CONTACT_DISPLAY (display));
606
607 if (display->priv->contact == contact)
608 return;
609
610 if (contact != NULL)
611 g_object_ref (contact);
612
613 if (display->priv->contact != NULL)
614 g_object_unref (display->priv->contact);
615
616 display->priv->contact = contact;
617
618 load_contact (display);
619
620 g_object_notify (G_OBJECT (display), "contact");
621 }
622
623 EABContactDisplayMode
624 eab_contact_display_get_mode (EABContactDisplay *display)
625 {
626 g_return_val_if_fail (EAB_IS_CONTACT_DISPLAY (display), 0);
627
628 return display->priv->mode;
629 }
630
631 void
632 eab_contact_display_set_mode (EABContactDisplay *display,
633 EABContactDisplayMode mode)
634 {
635 g_return_if_fail (EAB_IS_CONTACT_DISPLAY (display));
636
637 if (display->priv->mode == mode)
638 return;
639
640 display->priv->mode = mode;
641
642 load_contact (display);
643
644 g_object_notify (G_OBJECT (display), "mode");
645 }
646
647 gboolean
648 eab_contact_display_get_show_maps (EABContactDisplay *display)
649 {
650 g_return_val_if_fail (EAB_IS_CONTACT_DISPLAY (display), FALSE);
651
652 return display->priv->show_maps;
653 }
654
655 void
656 eab_contact_display_set_show_maps (EABContactDisplay *display,
657 gboolean show_maps)
658 {
659 g_return_if_fail (EAB_IS_CONTACT_DISPLAY (display));
660
661 if (display->priv->show_maps == show_maps)
662 return;
663
664 display->priv->show_maps = show_maps;
665
666 load_contact (display);
667
668 g_object_notify (G_OBJECT (display), "show-maps");
669 }