c9a44bf04dd58a713ca6d37348429bbec8356f6a
[empathy.git] / libempathy-gtk / gossip-private-chat.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2002-2007 Imendio AB
4  * Copyright (C) 2007 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Mikael Hallendal <micke@imendio.com>
22  *          Richard Hult <richard@imendio.com>
23  *          Martyn Russell <martyn@imendio.com>
24  *          Geert-Jan Van den Bogaerde <geertjan@gnome.org>
25  *          Xavier Claessens <xclaesse@gmail.com>
26  */
27
28 #include "config.h"
29
30 #include <string.h>
31
32 #include <gtk/gtk.h>
33 #include <glade/glade.h>
34 #include <glib/gi18n.h>
35
36 #include <libmissioncontrol/mc-account.h>
37
38 #include <libempathy/gossip-debug.h>
39 #include <libempathy/empathy-tp-chat.h>
40 //#include <libgossip/gossip-log.h>
41
42 #include "gossip-private-chat.h"
43 #include "gossip-chat-view.h"
44 #include "gossip-chat.h"
45 #include "gossip-preferences.h"
46 //#include "gossip-sound.h"
47 #include "gossip-stock.h"
48 #include "gossip-ui-utils.h"
49
50 #define DEBUG_DOMAIN "PrivateChat"
51
52 #define COMPOSING_STOP_TIMEOUT 5
53
54 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_PRIVATE_CHAT, GossipPrivateChatPriv))
55
56 struct _GossipPrivateChatPriv {   
57         GossipContact *contact;
58         gchar         *name;
59
60         guint          scroll_idle_id;
61         gboolean       is_online;
62
63         GtkWidget     *widget;
64         GtkWidget     *text_view_sw;
65 };
66
67 static void           gossip_private_chat_class_init            (GossipPrivateChatClass *klass);
68 static void           gossip_private_chat_init                  (GossipPrivateChat      *chat);
69 static void           private_chat_finalize                     (GObject                *object);
70 static void           private_chat_create_ui                    (GossipPrivateChat      *chat);
71 static void           private_chat_contact_presence_updated_cb  (GossipContact          *contact,
72                                                                  GParamSpec             *param,
73                                                                  GossipPrivateChat      *chat);
74 static void           private_chat_contact_updated_cb           (GossipContact          *contact,
75                                                                  GParamSpec             *param,
76                                                                  GossipPrivateChat      *chat);
77 static void           private_chat_widget_destroy_cb            (GtkWidget              *widget,
78                                                                  GossipPrivateChat      *chat);
79 static const gchar *  private_chat_get_name                     (GossipChat             *chat);
80 static gchar *        private_chat_get_tooltip                  (GossipChat             *chat);
81 static GdkPixbuf *    private_chat_get_status_pixbuf            (GossipChat             *chat);
82 static GossipContact *private_chat_get_contact                  (GossipChat             *chat);
83 static GtkWidget *    private_chat_get_widget                   (GossipChat             *chat);
84 /*static GdkPixbuf *    private_chat_pad_to_size                  (GdkPixbuf              *pixbuf,
85                                                                  gint                    width,
86                                                                  gint                    height,
87                                                                  gint                    extra_padding_right);*/
88
89 G_DEFINE_TYPE (GossipPrivateChat, gossip_private_chat, GOSSIP_TYPE_CHAT);
90
91 static void
92 gossip_private_chat_class_init (GossipPrivateChatClass *klass)
93 {
94         GObjectClass    *object_class = G_OBJECT_CLASS (klass);
95         GossipChatClass *chat_class = GOSSIP_CHAT_CLASS (klass);
96
97         object_class->finalize = private_chat_finalize;
98
99         chat_class->get_name          = private_chat_get_name;
100         chat_class->get_tooltip       = private_chat_get_tooltip;
101         chat_class->get_status_pixbuf = private_chat_get_status_pixbuf;
102         chat_class->get_contact       = private_chat_get_contact;
103         chat_class->get_widget        = private_chat_get_widget;
104         chat_class->get_show_contacts = NULL;
105         chat_class->set_show_contacts = NULL;
106         chat_class->is_group_chat     = NULL;
107
108         g_type_class_add_private (object_class, sizeof (GossipPrivateChatPriv));
109 }
110
111 static void
112 gossip_private_chat_init (GossipPrivateChat *chat)
113 {
114         GossipPrivateChatPriv *priv;
115
116         priv = GET_PRIV (chat);
117
118         priv->is_online = FALSE;
119
120         private_chat_create_ui (chat);
121
122 }
123
124 static void
125 private_chat_finalize (GObject *object)
126 {
127         GossipPrivateChat     *chat;
128         GossipPrivateChatPriv *priv;
129         
130         chat = GOSSIP_PRIVATE_CHAT (object);
131         priv = GET_PRIV (chat);
132
133         g_signal_handlers_disconnect_by_func (priv->contact,
134                                               private_chat_contact_updated_cb,
135                                               chat);
136         g_signal_handlers_disconnect_by_func (priv->contact,
137                                               private_chat_contact_presence_updated_cb,
138                                               chat);
139
140         if (priv->contact) {
141                 g_object_unref (priv->contact);
142         }
143
144         if (priv->scroll_idle_id) {
145                 g_source_remove (priv->scroll_idle_id);
146         }
147
148         g_free (priv->name);
149
150         G_OBJECT_CLASS (gossip_private_chat_parent_class)->finalize (object);
151 }
152
153 static void
154 private_chat_create_ui (GossipPrivateChat *chat)
155 {
156         GladeXML              *glade;
157         GossipPrivateChatPriv *priv;
158         GtkWidget             *input_text_view_sw;
159
160         priv = GET_PRIV (chat);
161
162         glade = gossip_glade_get_file ("gossip-chat.glade",
163                                        "chat_widget",
164                                        NULL,
165                                       "chat_widget", &priv->widget,
166                                       "chat_view_sw", &priv->text_view_sw,
167                                       "input_text_view_sw", &input_text_view_sw,
168                                        NULL);
169
170         gossip_glade_connect (glade,
171                               chat,
172                               "chat_widget", "destroy", private_chat_widget_destroy_cb,
173                               NULL);
174
175         g_object_unref (glade);
176
177         g_object_set_data (G_OBJECT (priv->widget), "chat", g_object_ref (chat));
178
179         gtk_container_add (GTK_CONTAINER (priv->text_view_sw),
180                            GTK_WIDGET (GOSSIP_CHAT (chat)->view));
181         gtk_widget_show (GTK_WIDGET (GOSSIP_CHAT (chat)->view));
182
183         gtk_container_add (GTK_CONTAINER (input_text_view_sw),
184                            GOSSIP_CHAT (chat)->input_text_view);
185         gtk_widget_show (GOSSIP_CHAT (chat)->input_text_view);
186 }
187
188 static void
189 private_chat_contact_presence_updated_cb (GossipContact     *contact,
190                                           GParamSpec        *param,
191                                           GossipPrivateChat *chat)
192 {
193         GossipPrivateChatPriv *priv;
194
195         priv = GET_PRIV (chat);
196
197         gossip_debug (DEBUG_DOMAIN, "Presence update for contact: %s",
198                       gossip_contact_get_id (contact));
199
200         if (!gossip_contact_is_online (contact)) {
201                 if (priv->is_online) {
202                         gchar *msg;
203
204                         msg = g_strdup_printf (_("%s went offline"),
205                                                gossip_contact_get_name (priv->contact));
206                         gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, msg);
207                         g_free (msg);
208                 }
209
210                 priv->is_online = FALSE;
211
212                 g_signal_emit_by_name (chat, "composing", FALSE);
213
214         } else {
215                 if (!priv->is_online) {
216                         gchar *msg;
217
218                         msg = g_strdup_printf (_("%s has come online"),
219                                                gossip_contact_get_name (priv->contact));
220                         gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, msg);
221                         g_free (msg);
222                 }
223
224                 priv->is_online = TRUE;
225         }
226
227         g_signal_emit_by_name (chat, "status-changed");
228 }
229
230 static void
231 private_chat_contact_updated_cb (GossipContact     *contact,
232                                  GParamSpec        *param,
233                                  GossipPrivateChat *chat)
234 {
235         GossipPrivateChatPriv *priv;
236
237         priv = GET_PRIV (chat);
238
239         if (strcmp (priv->name, gossip_contact_get_name (contact)) != 0) {
240                 g_free (priv->name);
241                 priv->name = g_strdup (gossip_contact_get_name (contact));
242                 g_signal_emit_by_name (chat, "name-changed", priv->name);
243         }
244 }
245
246 static void
247 private_chat_widget_destroy_cb (GtkWidget         *widget,
248                                 GossipPrivateChat *chat)
249 {
250         gossip_debug (DEBUG_DOMAIN, "Destroyed");
251
252         g_object_unref (chat);
253 }
254
255 static const gchar *
256 private_chat_get_name (GossipChat *chat)
257 {
258         GossipPrivateChatPriv *priv;
259
260         g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
261
262         priv = GET_PRIV (chat);
263
264         return priv->name;
265 }
266
267 static gchar *
268 private_chat_get_tooltip (GossipChat *chat)
269 {
270         GossipPrivateChatPriv *priv;
271         GossipContact         *contact;
272         const gchar           *status;
273
274         g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
275
276         priv = GET_PRIV (chat);
277
278         contact = gossip_chat_get_contact (chat);
279         status = gossip_contact_get_status (contact);
280
281         return g_strdup_printf ("%s\n%s",
282                                 gossip_contact_get_id (contact),
283                                 status);
284 }
285
286 static GdkPixbuf *
287 private_chat_get_status_pixbuf (GossipChat *chat)
288 {
289         GossipPrivateChatPriv *priv;
290         GossipContact         *contact;
291
292         g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
293
294         priv = GET_PRIV (chat);
295
296         contact = gossip_chat_get_contact (chat);
297
298         return gossip_pixbuf_for_contact (contact);
299 }
300
301 static GossipContact *
302 private_chat_get_contact (GossipChat *chat)
303 {
304         GossipPrivateChatPriv *priv;
305
306         g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
307
308         priv = GET_PRIV (chat);
309
310         return priv->contact;
311 }
312
313 static GtkWidget *
314 private_chat_get_widget (GossipChat *chat)
315 {
316         GossipPrivateChatPriv *priv;
317
318         priv = GET_PRIV (chat);
319
320         return priv->widget;
321 }
322
323 /* Scroll down after the back-log has been received. */
324 static gboolean
325 private_chat_scroll_down_idle_func (GossipChat *chat)
326 {
327         GossipPrivateChatPriv *priv;
328
329         priv = GET_PRIV (chat);
330
331         gossip_chat_scroll_down (chat);
332         g_object_unref (chat);
333
334         priv->scroll_idle_id = 0;
335
336         return FALSE;
337 }
338
339 static void
340 private_chat_setup (GossipPrivateChat *chat,
341                     GossipContact     *contact,
342                     EmpathyTpChat     *tp_chat)
343 {
344         GossipPrivateChatPriv *priv;
345         //GossipLogManager      *log_manager;
346         GossipChatView        *view;
347 /*      GossipContact         *sender;
348         GossipMessage         *message;
349         GList                 *messages, *l;
350         gint                   num_messages, i;*/
351
352         priv = GET_PRIV (chat);
353
354         gossip_chat_set_tp_chat (GOSSIP_CHAT (chat), tp_chat);
355
356         priv->contact = g_object_ref (contact);
357         GOSSIP_CHAT (chat)->account = g_object_ref (gossip_contact_get_account (contact));
358
359         priv->name = g_strdup (gossip_contact_get_name (contact));
360
361         g_signal_connect (priv->contact, 
362                           "notify::name",
363                           G_CALLBACK (private_chat_contact_updated_cb),
364                           chat);
365         g_signal_connect (priv->contact, 
366                           "notify::presence",
367                           G_CALLBACK (private_chat_contact_presence_updated_cb),
368                           chat);
369
370         view = GOSSIP_CHAT (chat)->view;
371
372         /* Turn off scrolling temporarily */
373         gossip_chat_view_scroll (view, FALSE);
374 #if 0
375 FIXME:
376         /* Add messages from last conversation */
377         log_manager = gossip_session_get_log_manager (gossip_app_get_session ());
378         messages = gossip_log_get_last_for_contact (log_manager, priv->contact);
379         num_messages  = g_list_length (messages);
380
381         for (l = messages, i = 0; l; l = l->next, i++) {
382                 message = l->data;
383
384                 if (num_messages - i > 10) {
385                         continue;
386                 }
387
388                 sender = gossip_message_get_sender (message);
389                 if (gossip_contact_equal (priv->own_contact, sender)) {
390                         gossip_chat_view_append_message_from_self (view,
391                                                                    message,
392                                                                    priv->own_contact,
393                                                                    priv->own_avatar);
394                 } else {
395                         gossip_chat_view_append_message_from_other (view,
396                                                                     message,
397                                                                     sender,
398                                                                     priv->other_avatar);
399                 }
400         }
401
402         g_list_foreach (messages, (GFunc) g_object_unref, NULL);
403         g_list_free (messages);
404 #endif
405         /* Turn back on scrolling */
406         gossip_chat_view_scroll (view, TRUE);
407
408         /* Scroll to the most recent messages, we reference the chat
409          * for the duration of the scroll func.
410          */
411         priv->scroll_idle_id = g_idle_add ((GSourceFunc) 
412                                            private_chat_scroll_down_idle_func, 
413                                            g_object_ref (chat));
414
415         priv->is_online = gossip_contact_is_online (priv->contact);
416 }
417
418 GossipPrivateChat *
419 gossip_private_chat_new (GossipContact *contact)
420 {
421         GossipPrivateChat *chat;
422         EmpathyTpChat     *tp_chat;
423
424         g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
425
426         chat = g_object_new (GOSSIP_TYPE_PRIVATE_CHAT, NULL);
427         tp_chat = empathy_tp_chat_new_with_contact (contact);
428
429         private_chat_setup (chat, contact, tp_chat);
430         g_object_unref (tp_chat);
431
432         return chat;
433 }
434
435 GossipPrivateChat *
436 gossip_private_chat_new_with_channel (GossipContact *contact,
437                                       TpChan        *tp_chan)
438 {
439         GossipPrivateChat *chat;
440         EmpathyTpChat     *tp_chat;
441         McAccount         *account;
442
443         g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
444         g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
445
446         account = gossip_contact_get_account (contact);
447         chat = g_object_new (GOSSIP_TYPE_PRIVATE_CHAT, NULL);
448         tp_chat = empathy_tp_chat_new (account, tp_chan);
449
450         private_chat_setup (chat, contact, tp_chat);
451         g_object_unref (tp_chat);
452
453         return chat;
454 }
455
456 /* Pads a pixbuf to the specified size, by centering it in a larger transparent
457  * pixbuf. Returns a new ref.
458  */
459 #if 0
460 FIXME:
461 static GdkPixbuf *
462 private_chat_pad_to_size (GdkPixbuf *pixbuf,
463                           gint       width,
464                           gint       height,
465                           gint       extra_padding_right)
466 {
467         gint       src_width, src_height;
468         GdkPixbuf *padded;
469         gint       x_offset, y_offset;
470
471         src_width = gdk_pixbuf_get_width (pixbuf);
472         src_height = gdk_pixbuf_get_height (pixbuf);
473
474         x_offset = (width - src_width) / 2;
475         y_offset = (height - src_height) / 2;
476
477         padded = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
478                                  TRUE, /* alpha */
479                                  gdk_pixbuf_get_bits_per_sample (pixbuf),
480                                  width + extra_padding_right,
481                                  height);
482
483         gdk_pixbuf_fill (padded, 0);
484
485         gdk_pixbuf_copy_area (pixbuf,
486                               0, /* source coords */
487                               0,
488                               src_width,
489                               src_height,
490                               padded,
491                               x_offset, /* dest coords */
492                               y_offset);
493
494         return padded;
495 }
496 #endif
497