]> git.0d.be Git - empathy.git/blob - libempathy-gtk/gossip-group-chat.c
New objects: GossipAccountChooser and GossipNewChatroom, we can now join
[empathy.git] / libempathy-gtk / gossip-group-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  *          Xavier Claessens <xclaesse@gmail.com>
25  */
26
27 #include "config.h"
28
29 #include <string.h>
30
31 #include <gtk/gtk.h>
32 #include <glade/glade.h>
33 #include <glib/gi18n.h>
34
35 #include <libempathy/empathy-tp-chat.h>
36 #include <libempathy/empathy-tp-chatroom.h>
37 #include <libempathy/gossip-contact.h>
38 #include <libempathy/gossip-utils.h>
39 #include <libempathy/gossip-debug.h>
40
41 #include "gossip-group-chat.h"
42 #include "gossip-chat.h"
43 #include "gossip-chat-view.h"
44 #include "gossip-contact-list-store.h"
45 #include "gossip-contact-list-view.h"
46 //#include "gossip-chat-invite.h"
47 //#include "gossip-sound.h"
48 #include "empathy-images.h"
49 #include "gossip-ui-utils.h"
50
51 #define DEBUG_DOMAIN "GroupChat"
52
53 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_GROUP_CHAT, GossipGroupChatPriv))
54
55 struct _GossipGroupChatPriv {
56         GossipContactListStore *store;
57         GossipContactListView  *view;
58         EmpathyTpChatroom      *tp_chat;
59
60         GtkWidget              *widget;
61         GtkWidget              *hpaned;
62         GtkWidget              *vbox_left;
63         GtkWidget              *scrolled_window_chat;
64         GtkWidget              *scrolled_window_input;
65         GtkWidget              *scrolled_window_contacts;
66         GtkWidget              *hbox_topic;
67         GtkWidget              *label_topic;
68
69         gchar                  *topic;
70         gchar                  *name;
71         GCompletion            *completion;
72
73         gint                    contacts_width;
74         gboolean                contacts_visible;
75 };
76
77 static void          group_chat_finalize                 (GObject           *object);
78 static void          group_chat_create_ui                (GossipGroupChat   *chat);
79 static void          group_chat_widget_destroy_cb        (GtkWidget         *widget,
80                                                           GossipGroupChat   *chat);
81 static void          group_chat_contact_added_cb         (EmpathyTpChatroom *tp_chat,
82                                                           GossipContact     *contact,
83                                                           GossipGroupChat   *chat);
84 static void          group_chat_contact_removed_cb       (EmpathyTpChatroom *tp_chat,
85                                                           GossipContact     *contact,
86                                                           GossipGroupChat   *chat);
87 /*static void          group_chat_topic_changed_cb         (EmpathyTpChatroom *tp_chat,
88                                                           const gchar       *new_topic,
89                                                           GossipGroupChat   *chat);*/
90 static void          group_chat_topic_entry_activate_cb  (GtkWidget         *entry,
91                                                           GtkDialog         *dialog);
92 static void          group_chat_topic_response_cb        (GtkWidget         *dialog,
93                                                           gint               response,                        
94                                                           GossipGroupChat   *chat);
95 void                 gossip_group_chat_set_topic         (GossipGroupChat   *chat);
96 static const gchar * group_chat_get_name                 (GossipChat        *chat);
97 static gchar *       group_chat_get_tooltip              (GossipChat        *chat);
98 static const gchar * group_chat_get_status_icon_name     (GossipChat        *chat);
99 static GtkWidget *   group_chat_get_widget               (GossipChat        *chat);
100 static gboolean      group_chat_is_group_chat            (GossipChat        *chat);
101 /*static gboolean      group_chat_key_press_event          (GtkWidget         *widget,
102                                                           GdkEventKey       *event,
103                                                           GossipGroupChat   *chat);*/
104 static gint          group_chat_contacts_completion_func (const gchar       *s1,
105                                                           const gchar       *s2,
106                                                           gsize              n);
107
108 G_DEFINE_TYPE (GossipGroupChat, gossip_group_chat, GOSSIP_TYPE_CHAT)
109
110 static void
111 gossip_group_chat_class_init (GossipGroupChatClass *klass)
112 {
113         GObjectClass    *object_class;
114         GossipChatClass *chat_class;
115
116         object_class = G_OBJECT_CLASS (klass);
117         chat_class = GOSSIP_CHAT_CLASS (klass);
118
119         object_class->finalize           = group_chat_finalize;
120
121         chat_class->get_name             = group_chat_get_name;
122         chat_class->get_tooltip          = group_chat_get_tooltip;
123         chat_class->get_status_icon_name = group_chat_get_status_icon_name;
124         chat_class->get_widget           = group_chat_get_widget;
125         chat_class->is_group_chat        = group_chat_is_group_chat;
126
127         g_type_class_add_private (object_class, sizeof (GossipGroupChatPriv));
128 }
129
130 static void
131 gossip_group_chat_init (GossipGroupChat *chat)
132 {
133         GossipGroupChatPriv *priv;
134         GossipChatView      *chatview;
135
136         priv = GET_PRIV (chat);
137
138         priv->contacts_visible = TRUE;
139
140         chatview = GOSSIP_CHAT_VIEW (GOSSIP_CHAT (chat)->view);
141         gossip_chat_view_set_is_group_chat (chatview, TRUE);
142
143         group_chat_create_ui (chat);
144 }
145
146 static void
147 group_chat_finalize (GObject *object)
148 {
149         GossipGroupChat     *chat;
150         GossipGroupChatPriv *priv;
151
152         gossip_debug (DEBUG_DOMAIN, "Finalized:%p", object);
153
154         chat = GOSSIP_GROUP_CHAT (object);
155         priv = GET_PRIV (chat);
156         
157         g_free (priv->name);
158         g_free (priv->topic);
159         g_object_unref (priv->store);
160         g_object_unref (priv->tp_chat); 
161         g_completion_free (priv->completion);
162
163         G_OBJECT_CLASS (gossip_group_chat_parent_class)->finalize (object);
164 }
165
166 GossipGroupChat *
167 gossip_group_chat_new (McAccount *account,
168                        TpChan    *tp_chan)
169 {
170         GossipGroupChat     *chat;
171         GossipGroupChatPriv *priv;
172
173         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
174         g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
175
176         chat = g_object_new (GOSSIP_TYPE_GROUP_CHAT, NULL);
177
178         priv = GET_PRIV (chat);
179
180         priv->tp_chat = empathy_tp_chatroom_new (account, tp_chan);
181         gossip_chat_set_tp_chat (GOSSIP_CHAT (chat), EMPATHY_TP_CHAT (priv->tp_chat));
182         GOSSIP_CHAT (chat)->account = g_object_ref (account);
183
184         /* FIXME: Ask the user before accepting */
185         empathy_tp_chatroom_accept_invitation (priv->tp_chat);
186
187         /* Create contact list */
188         priv->store = gossip_contact_list_store_new (EMPATHY_CONTACT_LIST (priv->tp_chat));
189         priv->view = gossip_contact_list_view_new (priv->store);
190         gossip_contact_list_store_set_show_offline (priv->store, TRUE);
191         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_contacts),
192                            GTK_WIDGET (priv->view));
193         gtk_widget_show (GTK_WIDGET (priv->view));
194
195         g_signal_connect (priv->tp_chat, "contact-added",
196                           G_CALLBACK (group_chat_contact_added_cb),
197                           chat);
198         g_signal_connect (priv->tp_chat, "contact-removed",
199                           G_CALLBACK (group_chat_contact_removed_cb),
200                           chat);
201 /*      g_signal_connect (priv->tp_chat, "chatroom-topic-changed",
202                           G_CALLBACK (group_chat_topic_changed_cb),
203                           chat);
204         g_signal_connect (priv->tp_chat, "contact-info-changed",
205                           G_CALLBACK (group_chat_contact_info_changed_cb),
206                           chat);*/
207
208         return chat;
209 }
210
211 gboolean
212 gossip_group_chat_get_show_contacts (GossipChat *chat)
213 {
214         GossipGroupChat     *group_chat;
215         GossipGroupChatPriv *priv;
216
217         g_return_val_if_fail (GOSSIP_IS_GROUP_CHAT (chat), FALSE);
218
219         group_chat = GOSSIP_GROUP_CHAT (chat);
220         priv = GET_PRIV (group_chat);
221
222         return priv->contacts_visible;
223 }
224
225 void
226 gossip_group_chat_set_show_contacts (GossipChat *chat,
227                                      gboolean    show)
228 {
229         GossipGroupChat     *group_chat;
230         GossipGroupChatPriv *priv;
231
232         g_return_if_fail (GOSSIP_IS_GROUP_CHAT (chat));
233
234         group_chat = GOSSIP_GROUP_CHAT (chat);
235         priv = GET_PRIV (group_chat);
236
237         priv->contacts_visible = show;
238
239         if (show) {
240                 gtk_widget_show (priv->scrolled_window_contacts);
241                 gtk_paned_set_position (GTK_PANED (priv->hpaned),
242                                         priv->contacts_width);
243         } else {
244                 priv->contacts_width = gtk_paned_get_position (GTK_PANED (priv->hpaned));
245                 gtk_widget_hide (priv->scrolled_window_contacts);
246         }
247 }
248
249 static void
250 group_chat_create_ui (GossipGroupChat *chat)
251 {
252         GossipGroupChatPriv *priv;
253         GladeXML            *glade;
254         GList               *list = NULL; 
255
256         priv = GET_PRIV (chat);
257
258         glade = gossip_glade_get_file ("gossip-group-chat.glade",
259                                        "group_chat_widget",
260                                        NULL,
261                                        "group_chat_widget", &priv->widget,
262                                        "hpaned", &priv->hpaned,
263                                        "vbox_left", &priv->vbox_left,
264                                        "scrolled_window_chat", &priv->scrolled_window_chat,
265                                        "scrolled_window_input", &priv->scrolled_window_input,
266                                        "hbox_topic", &priv->hbox_topic,
267                                        "label_topic", &priv->label_topic,
268                                        "scrolled_window_contacts", &priv->scrolled_window_contacts,
269                                        NULL);
270
271         gossip_glade_connect (glade,
272                               chat,
273                               "group_chat_widget", "destroy", group_chat_widget_destroy_cb,
274                               NULL);
275
276         g_object_unref (glade);
277
278         g_object_set_data (G_OBJECT (priv->widget), "chat", chat);
279
280         /* Add room GtkTextView. */
281         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_chat),
282                            GTK_WIDGET (GOSSIP_CHAT (chat)->view));
283         gtk_widget_show (GTK_WIDGET (GOSSIP_CHAT (chat)->view));
284
285         /* Add input GtkTextView */
286         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_input),
287                            GOSSIP_CHAT (chat)->input_text_view);
288         gtk_widget_show (GOSSIP_CHAT (chat)->input_text_view);
289
290         /* Add nick name completion */
291         priv->completion = g_completion_new (NULL);
292         g_completion_set_compare (priv->completion,
293                                   group_chat_contacts_completion_func);
294
295         /* Set widget focus order */
296         list = g_list_append (NULL, priv->scrolled_window_input);
297         gtk_container_set_focus_chain (GTK_CONTAINER (priv->vbox_left), list);
298         g_list_free (list);
299
300         list = g_list_append (NULL, priv->vbox_left);
301         list = g_list_append (list, priv->scrolled_window_contacts);
302         gtk_container_set_focus_chain (GTK_CONTAINER (priv->hpaned), list);
303         g_list_free (list);
304
305         list = g_list_append (NULL, priv->hpaned);
306         list = g_list_append (list, priv->hbox_topic);
307         gtk_container_set_focus_chain (GTK_CONTAINER (priv->widget), list);
308         g_list_free (list);
309 }
310
311 static void
312 group_chat_widget_destroy_cb (GtkWidget       *widget,
313                               GossipGroupChat *chat)
314 {
315         gossip_debug (DEBUG_DOMAIN, "Destroyed");
316
317         g_object_unref (chat);
318 }
319
320 static void
321 group_chat_contact_added_cb (EmpathyTpChatroom *tp_chat,
322                              GossipContact     *contact,
323                              GossipGroupChat   *chat)
324 {
325         GossipGroupChatPriv *priv;
326         gchar               *str;
327
328         priv = GET_PRIV (chat);
329
330         str = g_strdup_printf (_("%s has joined the room"),
331                                gossip_contact_get_name (contact));
332         gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, str);
333         g_free (str);
334 }
335
336 static void
337 group_chat_contact_removed_cb (EmpathyTpChatroom *tp_chat,
338                                GossipContact     *contact,
339                                GossipGroupChat   *chat)
340 {
341         GossipGroupChatPriv *priv;
342         gchar               *str;
343
344         priv = GET_PRIV (chat);
345
346         str = g_strdup_printf (_("%s has left the room"),
347                                gossip_contact_get_name (contact));
348         gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, str);
349         g_free (str);
350 }
351 /*
352 static void
353 group_chat_topic_changed_cb (EmpathyTpChatroom *tp_chat,
354                              const gchar       *new_topic,
355                              GossipGroupChat   *chat)
356 {
357         GossipGroupChatPriv *priv;
358         gchar               *str;
359
360         priv = GET_PRIV (chat);
361
362         gossip_debug (DEBUG_DOMAIN, "Topic changed by to:'%s'", new_topic);
363
364         g_free (priv->topic);
365         priv->topic = g_strdup (new_topic);
366         
367         gtk_label_set_text (GTK_LABEL (priv->label_topic), new_topic);
368
369         str = g_strdup_printf (_("Topic set to: %s"), new_topic);
370         gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, str);
371         g_free (str);
372 }
373 */
374 static void
375 group_chat_topic_entry_activate_cb (GtkWidget *entry,
376                                     GtkDialog *dialog)
377 {
378         gtk_dialog_response (dialog, GTK_RESPONSE_OK);
379 }
380
381 static void
382 group_chat_topic_response_cb (GtkWidget       *dialog,
383                               gint             response,                              
384                               GossipGroupChat *chat)
385 {
386         if (response == GTK_RESPONSE_OK) {
387                 GtkWidget   *entry;
388                 const gchar *topic;
389
390                 entry = g_object_get_data (G_OBJECT (dialog), "entry");
391                 topic = gtk_entry_get_text (GTK_ENTRY (entry));
392                 
393                 if (!G_STR_EMPTY (topic)) {
394                         GossipGroupChatPriv *priv;
395
396                         priv = GET_PRIV (chat);
397
398                         empathy_tp_chatroom_set_topic (priv->tp_chat, topic);
399                 }
400         }
401
402         gtk_widget_destroy (dialog);
403 }
404
405 void
406 gossip_group_chat_set_topic (GossipGroupChat *chat)
407 {
408         GossipGroupChatPriv *priv;
409         GossipChatWindow    *chat_window;
410         GtkWidget           *chat_dialog;
411         GtkWidget           *dialog;
412         GtkWidget           *entry;
413         GtkWidget           *hbox;
414         const gchar         *topic;
415
416         priv = GET_PRIV (chat);
417
418         chat_window = gossip_chat_get_window (GOSSIP_CHAT (chat));
419         chat_dialog = gossip_chat_window_get_dialog (chat_window);
420
421         dialog = gtk_message_dialog_new (GTK_WINDOW (chat_dialog),
422                                          0,
423                                          GTK_MESSAGE_QUESTION,
424                                          GTK_BUTTONS_OK_CANCEL,
425                                          _("Enter the new topic you want to set for this room:"));
426
427         topic = gtk_label_get_text (GTK_LABEL (priv->label_topic));
428
429         hbox = gtk_hbox_new (FALSE, 0);
430         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
431                             hbox, FALSE, TRUE, 4);
432
433         entry = gtk_entry_new ();
434         gtk_entry_set_text (GTK_ENTRY (entry), topic);
435         gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
436                     
437         gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 4);
438
439         g_object_set (GTK_MESSAGE_DIALOG (dialog)->label, "use-markup", TRUE, NULL);
440         g_object_set_data (G_OBJECT (dialog), "entry", entry);
441
442         g_signal_connect (entry, "activate",
443                           G_CALLBACK (group_chat_topic_entry_activate_cb),
444                           dialog);
445         g_signal_connect (dialog, "response",
446                           G_CALLBACK (group_chat_topic_response_cb),
447                           chat);
448
449         gtk_widget_show_all (dialog);
450 }
451
452 static const gchar *
453 group_chat_get_name (GossipChat *chat)
454 {
455         GossipGroupChat     *group_chat;
456         GossipGroupChatPriv *priv;
457
458         g_return_val_if_fail (GOSSIP_IS_GROUP_CHAT (chat), NULL);
459
460         group_chat = GOSSIP_GROUP_CHAT (chat);
461         priv = GET_PRIV (group_chat);
462
463         return priv->name;
464 }
465
466 static gchar *
467 group_chat_get_tooltip (GossipChat *chat)
468 {
469         GossipGroupChat     *group_chat;
470         GossipGroupChatPriv *priv;
471
472         g_return_val_if_fail (GOSSIP_IS_GROUP_CHAT (chat), NULL);
473
474         group_chat = GOSSIP_GROUP_CHAT (chat);
475         priv = GET_PRIV (group_chat);
476
477         if (priv->topic) {
478                 gchar *topic, *tmp;
479
480                 topic = g_strdup_printf (_("Topic: %s"), priv->topic);
481                 tmp = g_strdup_printf ("%s\n%s", priv->name, topic);
482                 g_free (topic);
483
484                 return tmp;
485         }
486
487         return g_strdup (priv->name);
488 }
489
490 static const gchar *
491 group_chat_get_status_icon_name (GossipChat *chat)
492 {
493         return EMPATHY_IMAGE_GROUP_MESSAGE;
494 }
495
496 static GtkWidget *
497 group_chat_get_widget (GossipChat *chat)
498 {
499         GossipGroupChat     *group_chat;
500         GossipGroupChatPriv *priv;
501
502         g_return_val_if_fail (GOSSIP_IS_GROUP_CHAT (chat), NULL);
503
504         group_chat = GOSSIP_GROUP_CHAT (chat);
505         priv = GET_PRIV (group_chat);
506
507         return priv->widget;
508 }
509
510 static gboolean
511 group_chat_is_group_chat (GossipChat *chat)
512 {
513         g_return_val_if_fail (GOSSIP_IS_GROUP_CHAT (chat), FALSE);
514
515         return TRUE;
516 }
517 #if 0
518 static gboolean
519 group_chat_key_press_event (GtkWidget       *widget,
520                             GdkEventKey     *event,
521                             GossipGroupChat *chat)
522 {
523         GossipGroupChatPriv *priv;
524         GtkAdjustment       *adj;
525         gdouble              val;
526         GtkTextBuffer       *buffer;
527         GtkTextIter          start, current;
528         gchar               *nick, *completed;
529         gint                 len;
530         GList               *list, *l, *completed_list;
531         gboolean             is_start_of_buffer;
532
533         priv = GET_PRIV (chat);
534
535         if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
536             (event->state & GDK_SHIFT_MASK) != GDK_SHIFT_MASK &&
537             event->keyval == GDK_Tab) {
538                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (GOSSIP_CHAT (chat)->input_text_view));
539                 gtk_text_buffer_get_iter_at_mark (buffer, &current, gtk_text_buffer_get_insert (buffer));
540
541                 /* Get the start of the nick to complete. */
542                 gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer));
543                 gtk_text_iter_backward_word_start (&start);
544                 is_start_of_buffer = gtk_text_iter_is_start (&start);
545
546                 nick = gtk_text_buffer_get_text (buffer, &start, &current, FALSE);
547
548                 g_completion_clear_items (priv->completion);
549
550                 len = strlen (nick);
551
552                 list = group_chat_get_nick_list (chat);
553
554                 g_completion_add_items (priv->completion, list);
555
556                 completed_list = g_completion_complete (priv->completion,
557                                                         nick,
558                                                         &completed);
559
560                 g_free (nick);
561
562                 if (completed) {
563                         int       len;
564                         gchar    *text;
565
566                         gtk_text_buffer_delete (buffer, &start, &current);
567
568                         len = g_list_length (completed_list);
569
570                         if (len == 1) {
571                                 /* If we only have one hit, use that text
572                                  * instead of the text in completed since the
573                                  * completed text will use the typed string
574                                  * which might be cased all wrong.
575                                  * Fixes #120876
576                                  * */
577                                 text = (gchar *) completed_list->data;
578                         } else {
579                                 text = completed;
580                         }
581
582                         gtk_text_buffer_insert_at_cursor (buffer, text, strlen (text));
583
584                         if (len == 1) {
585                                 if (is_start_of_buffer) {
586                                         gtk_text_buffer_insert_at_cursor (buffer, ", ", 2);
587                                 }
588                         }
589
590                         g_free (completed);
591                 }
592
593                 g_completion_clear_items (priv->completion);
594
595                 for (l = list; l; l = l->next) {
596                         g_free (l->data);
597                 }
598
599                 g_list_free (list);
600
601                 return TRUE;
602         }
603
604         return FALSE;
605 }
606 #endif
607
608 static gint
609 group_chat_contacts_completion_func (const gchar *s1,
610                                      const gchar *s2,
611                                      gsize        n)
612 {
613         gchar *tmp, *nick1, *nick2;
614         gint   ret;
615
616         tmp = g_utf8_normalize (s1, -1, G_NORMALIZE_DEFAULT);
617         nick1 = g_utf8_casefold (tmp, -1);
618         g_free (tmp);
619
620         tmp = g_utf8_normalize (s2, -1, G_NORMALIZE_DEFAULT);
621         nick2 = g_utf8_casefold (tmp, -1);
622         g_free (tmp);
623
624         ret = strncmp (nick1, nick2, n);
625
626         g_free (nick1);
627         g_free (nick2);
628
629         return ret;
630 }
631