Help: Use stable 'if' namespace instead of experimental
[empathy-mirror.git] / src / empathy-chat-window.c
blob3457defff14c39c9b22eea412ad5d7e42420c43a
1 /*
2 * Copyright (C) 2003-2007 Imendio AB
3 * Copyright (C) 2007-2012 Collabora Ltd.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
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 GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301 USA
20 * Authors: Mikael Hallendal <micke@imendio.com>
21 * Richard Hult <richard@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
24 * Xavier Claessens <xclaesse@gmail.com>
25 * Rômulo Fernandes Machado <romulo@castorgroup.net>
28 #include "config.h"
29 #include "empathy-chat-window.h"
31 #include <glib/gi18n.h>
32 #include <tp-account-widgets/tpaw-builder.h>
33 #include <tp-account-widgets/tpaw-utils.h>
35 #include "empathy-about-dialog.h"
36 #include "empathy-chat-manager.h"
37 #include "empathy-chatroom-manager.h"
38 #include "empathy-client-factory.h"
39 #include "empathy-geometry.h"
40 #include "empathy-gsettings.h"
41 #include "empathy-images.h"
42 #include "empathy-invite-participant-dialog.h"
43 #include "empathy-notify-manager.h"
44 #include "empathy-request-util.h"
45 #include "empathy-sound-manager.h"
46 #include "empathy-ui-utils.h"
47 #include "empathy-utils.h"
48 #include "empathy-new-message-dialog.h"
50 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
51 #include "empathy-debug.h"
53 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
55 #define X_EARLIER_OR_EQL(t1, t2) \
56 ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
57 || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
60 enum
62 PROP_INDIVIDUAL_MGR = 1
65 struct _EmpathyChatWindowPriv
67 EmpathyChat *current_chat;
68 GList *chats;
69 gboolean page_added;
70 gboolean dnd_same_window;
71 EmpathyChatroomManager *chatroom_manager;
72 EmpathyNotifyManager *notify_mgr;
73 EmpathyIndividualManager *individual_mgr;
74 GtkWidget *notebook;
75 NotifyNotification *notification;
77 GtkTargetList *contact_targets;
78 GtkTargetList *file_targets;
80 EmpathyChatManager *chat_manager;
81 gulong chat_manager_chats_changed_id;
83 /* Menu items. */
84 GtkUIManager *ui_manager;
85 GtkAction *menu_conv_insert_smiley;
86 GtkAction *menu_conv_favorite;
87 GtkAction *menu_conv_join_chat;
88 GtkAction *menu_conv_leave_chat;
89 GtkAction *menu_conv_always_urgent;
90 GtkAction *menu_conv_toggle_contacts;
92 GtkAction *menu_edit_cut;
93 GtkAction *menu_edit_copy;
94 GtkAction *menu_edit_paste;
95 GtkAction *menu_edit_find;
97 GtkAction *menu_tabs_next;
98 GtkAction *menu_tabs_prev;
99 GtkAction *menu_tabs_undo_close_tab;
100 GtkAction *menu_tabs_left;
101 GtkAction *menu_tabs_right;
102 GtkAction *menu_tabs_detach;
104 /* Last user action time we acted upon to show a tab */
105 guint32 x_user_action_time;
107 GSettings *gsettings_chat;
108 GSettings *gsettings_notif;
109 GSettings *gsettings_ui;
111 EmpathySoundManager *sound_mgr;
113 gboolean updating_menu;
116 static GList *chat_windows = NULL;
118 static const guint tab_accel_keys[] =
120 GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
121 GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
124 typedef enum
126 DND_DRAG_TYPE_CONTACT_ID,
127 DND_DRAG_TYPE_INDIVIDUAL_ID,
128 DND_DRAG_TYPE_URI_LIST,
129 DND_DRAG_TYPE_TAB
130 } DndDragType;
132 static const GtkTargetEntry drag_types_dest[] =
134 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
135 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
136 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
137 /* FIXME: disabled because of bug #640513
138 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
139 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
143 static const GtkTargetEntry drag_types_dest_contact[] =
145 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
146 { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
149 static const GtkTargetEntry drag_types_dest_file[] =
151 /* must be first to be prioritized, in order to receive the
152 * note's file path from Tomboy instead of an URI */
153 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
154 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
157 static void chat_window_update (EmpathyChatWindow *window,
158 gboolean update_contact_menu);
160 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
161 EmpathyChat *chat);
163 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
164 EmpathyChat *chat);
166 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
167 EmpathyChatWindow *new_window,
168 EmpathyChat *chat);
170 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
171 guint *nb_rooms,
172 guint *nb_private);
174 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, GTK_TYPE_BIN)
176 static void
177 chat_window_accel_cb (GtkAccelGroup *accelgroup,
178 GObject *object,
179 guint key,
180 GdkModifierType mod,
181 EmpathyChatWindow *self)
183 gint num = -1;
184 guint i;
186 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
188 if (tab_accel_keys[i] == key)
190 num = i;
191 break;
195 if (num != -1)
196 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
199 static EmpathyChatWindow *
200 chat_window_find_chat (EmpathyChat *chat)
202 GList *l, *ll;
204 for (l = chat_windows; l; l = l->next)
206 EmpathyChatWindow *window = l->data;
208 ll = g_list_find (window->priv->chats, chat);
209 if (ll)
210 return l->data;
213 return NULL;
216 static void
217 remove_all_chats (EmpathyChatWindow *self)
219 g_object_ref (self);
221 while (self->priv->chats)
222 empathy_chat_window_remove_chat (self, self->priv->chats->data);
224 g_object_unref (self);
227 static void
228 confirm_close_response_cb (GtkWidget *dialog,
229 int response,
230 EmpathyChatWindow *window)
232 EmpathyChat *chat;
234 chat = g_object_get_data (G_OBJECT (dialog), "chat");
236 gtk_widget_destroy (dialog);
238 if (response != GTK_RESPONSE_ACCEPT)
239 return;
241 if (chat != NULL)
242 empathy_chat_window_remove_chat (window, chat);
243 else
244 remove_all_chats (window);
247 static void
248 confirm_close (EmpathyChatWindow *self,
249 gboolean close_window,
250 guint n_rooms,
251 EmpathyChat *chat)
253 GtkWidget *dialog;
254 gchar *primary, *secondary;
256 g_return_if_fail (n_rooms > 0);
258 if (n_rooms > 1)
259 g_return_if_fail (chat == NULL);
260 else
261 g_return_if_fail (chat != NULL);
263 /* If there are no chats in this window, how could we possibly have got
264 * here?
266 g_return_if_fail (self->priv->chats != NULL);
268 /* Treat closing a window which only has one tab exactly like closing
269 * that tab.
271 if (close_window && self->priv->chats->next == NULL)
273 close_window = FALSE;
274 chat = self->priv->chats->data;
277 if (close_window)
279 primary = g_strdup (_("Close this window?"));
281 if (n_rooms == 1)
283 gchar *chat_name = empathy_chat_dup_name (chat);
284 secondary = g_strdup_printf (
285 _("Closing this window will leave %s. You will "
286 "not receive any further messages until you "
287 "rejoin it."),
288 chat_name);
289 g_free (chat_name);
291 else
293 secondary = g_strdup_printf (
294 ngettext (
295 /* Note to translators: the number of chats will
296 * always be at least 2.
298 "Closing this window will leave a chat room. You will "
299 "not receive any further messages until you rejoin it.",
300 "Closing this window will leave %u chat rooms. You will "
301 "not receive any further messages until you rejoin them.",
302 n_rooms),
303 n_rooms);
306 else
308 gchar *chat_name = empathy_chat_dup_name (chat);
309 primary = g_strdup_printf (_("Leave %s?"), chat_name);
310 secondary = g_strdup (
311 _("You will not receive any further messages from this chat "
312 "room until you rejoin it."));
313 g_free (chat_name);
316 dialog = gtk_message_dialog_new (
317 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))),
318 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
319 GTK_MESSAGE_WARNING,
320 GTK_BUTTONS_CANCEL,
321 "%s", primary);
323 gtk_window_set_title (GTK_WINDOW (dialog), "");
324 g_object_set (dialog, "secondary-text", secondary, NULL);
326 g_free (primary);
327 g_free (secondary);
329 gtk_dialog_add_button (GTK_DIALOG (dialog),
330 close_window ? _("Close window") : _("Leave room"),
331 GTK_RESPONSE_ACCEPT);
332 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
333 GTK_RESPONSE_ACCEPT);
335 if (!close_window)
336 g_object_set_data (G_OBJECT (dialog), "chat", chat);
338 g_signal_connect (dialog, "response",
339 G_CALLBACK (confirm_close_response_cb), self);
341 gtk_window_present (GTK_WINDOW (dialog));
344 /* Returns TRUE if we should check if the user really wants to leave. If it's
345 * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
346 * the user is actually in the room as opposed to having been kicked or gone
347 * offline or something), then we should check.
349 static gboolean
350 chat_needs_close_confirmation (EmpathyChat *chat)
352 return (empathy_chat_is_room (chat) &&
353 empathy_chat_get_tp_chat (chat) != NULL);
356 static void
357 maybe_close_chat (EmpathyChatWindow *window,
358 EmpathyChat *chat)
360 g_return_if_fail (chat != NULL);
362 if (chat_needs_close_confirmation (chat))
363 confirm_close (window, FALSE, 1, chat);
364 else
365 empathy_chat_window_remove_chat (window, chat);
368 static void
369 chat_window_close_clicked_cb (GtkAction *action,
370 EmpathyChat *chat)
372 EmpathyChatWindow *window;
374 window = chat_window_find_chat (chat);
375 maybe_close_chat (window, chat);
378 static void
379 chat_tab_style_updated_cb (GtkWidget *hbox,
380 gpointer user_data)
382 GtkWidget *button;
383 int char_width, h, w;
384 GtkStyleContext *style_context;
385 PangoContext *pango_context;
386 PangoFontDescription *font_desc;
387 PangoFontMetrics *metrics;
389 button = g_object_get_data (G_OBJECT (user_data),
390 "chat-window-tab-close-button");
391 style_context = gtk_widget_get_style_context (hbox);
392 pango_context = gtk_widget_get_pango_context (hbox);
394 gtk_style_context_save (style_context);
395 gtk_style_context_set_state (style_context, GTK_STATE_FLAG_NORMAL);
396 gtk_style_context_get (style_context,
397 GTK_STATE_FLAG_NORMAL,
398 "font", &font_desc,
399 NULL);
400 gtk_style_context_restore (style_context);
402 metrics = pango_context_get_metrics (pango_context, font_desc,
403 pango_context_get_language (pango_context));
404 char_width = pango_font_metrics_get_approximate_char_width (metrics);
405 pango_font_metrics_unref (metrics);
407 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
408 GTK_ICON_SIZE_MENU, &w, &h);
410 /* Request at least about 12 chars width plus at least space for the status
411 * image and the close button */
412 gtk_widget_set_size_request (hbox,
413 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
415 gtk_widget_set_size_request (button, w, h);
416 pango_font_description_free (font_desc);
419 static GtkWidget *
420 create_close_button (void)
422 GtkWidget *button, *image;
423 GtkStyleContext *context;
425 button = gtk_button_new ();
427 context = gtk_widget_get_style_context (button);
428 gtk_style_context_add_class (context, "empathy-tab-close-button");
430 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
431 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
433 /* We don't want focus/keynav for the button to avoid clutter, and
434 * Ctrl-W works anyway.
436 gtk_widget_set_can_focus (button, FALSE);
437 gtk_widget_set_can_default (button, FALSE);
439 image = gtk_image_new_from_icon_name ("window-close-symbolic",
440 GTK_ICON_SIZE_MENU);
441 gtk_widget_show (image);
443 gtk_container_add (GTK_CONTAINER (button), image);
445 return button;
448 static GtkWidget *
449 chat_window_create_label (EmpathyChatWindow *window,
450 EmpathyChat *chat,
451 gboolean is_tab_label)
453 GtkWidget *hbox;
454 GtkWidget *name_label;
455 GtkWidget *status_image;
456 GtkWidget *event_box;
457 GtkWidget *event_box_hbox;
458 PangoAttrList *attr_list;
459 PangoAttribute *attr;
461 /* The spacing between the button and the label. */
462 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
464 event_box = gtk_event_box_new ();
465 gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
467 name_label = gtk_label_new (NULL);
468 if (is_tab_label)
469 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
471 attr_list = pango_attr_list_new ();
472 attr = pango_attr_scale_new (1/1.2);
473 attr->start_index = 0;
474 attr->end_index = -1;
475 pango_attr_list_insert (attr_list, attr);
476 gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
477 pango_attr_list_unref (attr_list);
479 gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
480 gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
481 g_object_set_data (G_OBJECT (chat),
482 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
483 name_label);
485 status_image = gtk_image_new ();
487 /* Spacing between the icon and label. */
488 event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
490 gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
491 gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
493 g_object_set_data (G_OBJECT (chat),
494 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
495 status_image);
496 g_object_set_data (G_OBJECT (chat),
497 is_tab_label ? "chat-window-tab-tooltip-widget" :
498 "chat-window-menu-tooltip-widget",
499 event_box);
501 gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
502 gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
504 if (is_tab_label)
506 GtkWidget *close_button;
507 GtkWidget *sending_spinner;
509 sending_spinner = gtk_spinner_new ();
511 gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
512 FALSE, FALSE, 0);
513 g_object_set_data (G_OBJECT (chat),
514 "chat-window-tab-sending-spinner",
515 sending_spinner);
517 close_button = create_close_button ();
518 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
519 close_button);
521 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
523 g_signal_connect (close_button,
524 "clicked",
525 G_CALLBACK (chat_window_close_clicked_cb), chat);
527 /* React to theme changes and also setup the size correctly. */
528 g_signal_connect (hbox, "style-updated",
529 G_CALLBACK (chat_tab_style_updated_cb), chat);
532 gtk_widget_show_all (hbox);
534 return hbox;
537 static void
538 _submenu_notify_visible_changed_cb (GObject *object,
539 GParamSpec *pspec,
540 gpointer userdata)
542 g_signal_handlers_disconnect_by_func (object,
543 _submenu_notify_visible_changed_cb, userdata);
545 chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
548 static void
549 chat_window_menu_context_update (EmpathyChatWindow *self,
550 gint num_pages)
552 gboolean first_page;
553 gboolean last_page;
554 gboolean wrap_around;
555 gboolean is_connected;
556 gint page_num;
558 page_num = gtk_notebook_get_current_page (
559 GTK_NOTEBOOK (self->priv->notebook));
560 first_page = (page_num == 0);
561 last_page = (page_num == (num_pages - 1));
562 g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
563 &wrap_around, NULL);
564 is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
566 gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
567 wrap_around));
568 gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
569 wrap_around));
570 gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
571 gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
572 gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
573 gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
576 static void
577 chat_window_conversation_menu_update (EmpathyChatWindow *self)
579 EmpathyTpChat *tp_chat;
580 TpConnection *connection;
581 GtkAction *action;
582 gboolean sensitive = FALSE;
584 g_return_if_fail (self->priv->current_chat != NULL);
586 action = gtk_ui_manager_get_action (self->priv->ui_manager,
587 "/chats_menubar/menu_conv/menu_conv_invite_participant");
588 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
590 if (tp_chat != NULL)
592 connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
594 sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
595 (tp_connection_get_status (connection, NULL) ==
596 TP_CONNECTION_STATUS_CONNECTED);
599 gtk_action_set_sensitive (action, sensitive);
602 static void
603 chat_window_contact_menu_update (EmpathyChatWindow *self)
605 GtkWidget *menu, *submenu, *orig_submenu;
607 if (self->priv->current_chat == NULL)
608 return;
610 if (self->priv->updating_menu)
611 return;
612 self->priv->updating_menu = TRUE;
614 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
615 "/chats_menubar/menu_contact");
616 orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
618 if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
620 submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
622 if (submenu != NULL)
624 /* gtk_menu_attach_to_widget () doesn't behave nicely here */
625 g_object_set_data (G_OBJECT (submenu), "window", self);
627 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
628 gtk_widget_show (menu);
629 gtk_widget_set_sensitive (menu, TRUE);
631 else
633 gtk_widget_set_sensitive (menu, FALSE);
636 else
638 tp_g_signal_connect_object (orig_submenu,
639 "notify::visible",
640 (GCallback)_submenu_notify_visible_changed_cb, self, 0);
643 self->priv->updating_menu = FALSE;
646 static guint
647 get_all_unread_messages (EmpathyChatWindow *self)
649 GList *l;
650 guint nb = 0;
652 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
653 nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
655 return nb;
658 static gchar *
659 get_window_title_name (EmpathyChatWindow *self)
661 gchar *active_name, *ret;
662 guint nb_chats;
663 guint current_unread_msgs;
665 nb_chats = g_list_length (self->priv->chats);
666 g_assert (nb_chats > 0);
668 active_name = empathy_chat_dup_name (self->priv->current_chat);
670 current_unread_msgs = empathy_chat_get_nb_unread_messages (
671 self->priv->current_chat);
673 if (nb_chats == 1)
675 /* only one tab */
676 if (current_unread_msgs == 0)
677 ret = g_strdup (active_name);
678 else
679 ret = g_strdup_printf (ngettext (
680 "%s (%d unread)",
681 "%s (%d unread)", current_unread_msgs),
682 active_name, current_unread_msgs);
684 else
686 guint nb_others = nb_chats - 1;
687 guint all_unread_msgs;
689 all_unread_msgs = get_all_unread_messages (self);
691 if (all_unread_msgs == 0)
693 /* no unread message */
694 ret = g_strdup_printf (ngettext (
695 "%s (and %u other)",
696 "%s (and %u others)", nb_others),
697 active_name, nb_others);
699 else if (all_unread_msgs == current_unread_msgs)
701 /* unread messages are in the current tab */
702 ret = g_strdup_printf (ngettext (
703 "%s (%d unread)",
704 "%s (%d unread)", current_unread_msgs),
705 active_name, current_unread_msgs);
707 else if (current_unread_msgs == 0)
709 /* unread messages are in other tabs */
710 ret = g_strdup_printf (ngettext (
711 "%s (%d unread from others)",
712 "%s (%d unread from others)",
713 all_unread_msgs),
714 active_name, all_unread_msgs);
716 else
718 /* unread messages are in all the tabs */
719 ret = g_strdup_printf (ngettext (
720 "%s (%d unread from all)",
721 "%s (%d unread from all)",
722 all_unread_msgs),
723 active_name, all_unread_msgs);
727 g_free (active_name);
729 return ret;
732 static void
733 chat_window_title_update (EmpathyChatWindow *self)
735 gchar *name;
737 name = get_window_title_name (self);
738 //gtk_window_set_title (GTK_WINDOW (self), name);
739 g_free (name);
742 static void
743 chat_window_icon_update (EmpathyChatWindow *self,
744 gboolean new_messages)
746 GdkPixbuf *icon;
747 EmpathyContact *remote_contact;
748 gboolean avatar_in_icon;
749 guint n_chats;
751 n_chats = g_list_length (self->priv->chats);
753 /* Update window icon */
754 if (new_messages)
756 //gtk_window_set_icon_name (GTK_WINDOW (self),
757 // EMPATHY_IMAGE_MESSAGE);
759 else
761 avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
762 EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
764 if (n_chats == 1 && avatar_in_icon)
766 remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
767 icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
768 0, 0);
769 //gtk_window_set_icon (GTK_WINDOW (self), icon);
771 if (icon != NULL)
772 g_object_unref (icon);
774 else
776 //gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
781 static void
782 chat_window_close_button_update (EmpathyChatWindow *self,
783 gint num_pages)
785 GtkWidget *chat;
786 GtkWidget *chat_close_button;
787 gint i;
789 if (num_pages == 1)
791 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
792 chat_close_button = g_object_get_data (G_OBJECT (chat),
793 "chat-window-tab-close-button");
794 gtk_widget_hide (chat_close_button);
796 else
798 for (i=0; i<num_pages; i++)
800 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
801 chat_close_button = g_object_get_data (G_OBJECT (chat),
802 "chat-window-tab-close-button");
803 gtk_widget_show (chat_close_button);
808 static void
809 chat_window_update (EmpathyChatWindow *self,
810 gboolean update_contact_menu)
812 gint num_pages;
814 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
816 /* Update Tab menu */
817 chat_window_menu_context_update (self, num_pages);
819 chat_window_conversation_menu_update (self);
821 /* If this update is due to a focus-in event, we know the menu will be
822 the same as when we last left it, so no work to do. Besides, if we
823 swap out the menu on a focus-in, we may confuse any external global
824 menu watching. */
825 if (update_contact_menu)
827 chat_window_contact_menu_update (self);
830 chat_window_title_update (self);
832 chat_window_icon_update (self, get_all_unread_messages (self) > 0);
834 chat_window_close_button_update (self, num_pages);
837 static void
838 append_markup_printf (GString *string,
839 const char *format,
840 ...)
842 gchar *tmp;
843 va_list args;
845 va_start (args, format);
847 tmp = g_markup_vprintf_escaped (format, args);
848 g_string_append (string, tmp);
849 g_free (tmp);
851 va_end (args);
854 static void
855 chat_window_update_chat_tab_full (EmpathyChat *chat,
856 gboolean update_contact_menu)
858 EmpathyChatWindow *self;
859 EmpathyContact *remote_contact;
860 gchar *name;
861 const gchar *id;
862 TpAccount *account;
863 const gchar *subject;
864 const gchar *status = NULL;
865 GtkWidget *widget;
866 GString *tooltip;
867 gchar *markup;
868 const gchar *icon_name;
869 GtkWidget *tab_image;
870 GtkWidget *menu_image;
871 GtkWidget *sending_spinner;
872 guint nb_sending;
874 self = chat_window_find_chat (chat);
875 if (!self)
876 return;
878 /* Get information */
879 name = empathy_chat_dup_name (chat);
880 account = empathy_chat_get_account (chat);
881 subject = empathy_chat_get_subject (chat);
882 remote_contact = empathy_chat_get_remote_contact (chat);
884 DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
885 "remote_contact=%p",
886 name, tp_proxy_get_object_path (account), subject, remote_contact);
888 /* Update tab image */
889 if (empathy_chat_get_tp_chat (chat) == NULL)
891 /* No TpChat, we are disconnected */
892 icon_name = NULL;
894 else if (empathy_chat_get_nb_unread_messages (chat) > 0)
896 icon_name = EMPATHY_IMAGE_MESSAGE;
898 else if (remote_contact && empathy_chat_is_composing (chat))
900 icon_name = EMPATHY_IMAGE_TYPING;
902 else if (empathy_chat_is_sms_channel (chat))
904 icon_name = EMPATHY_IMAGE_SMS;
906 else if (remote_contact)
908 icon_name = empathy_icon_name_for_contact (remote_contact);
910 else
912 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
915 tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
916 menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
918 if (icon_name != NULL)
920 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
921 GTK_ICON_SIZE_MENU);
922 gtk_widget_show (tab_image);
923 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
924 GTK_ICON_SIZE_MENU);
925 gtk_widget_show (menu_image);
927 else
929 gtk_widget_hide (tab_image);
930 gtk_widget_hide (menu_image);
933 /* Update the sending spinner */
934 nb_sending = empathy_chat_get_n_messages_sending (chat);
935 sending_spinner = g_object_get_data (G_OBJECT (chat),
936 "chat-window-tab-sending-spinner");
938 g_object_set (sending_spinner,
939 "active", nb_sending > 0,
940 "visible", nb_sending > 0,
941 NULL);
943 /* Update tab tooltip */
944 tooltip = g_string_new (NULL);
946 if (remote_contact)
948 id = empathy_contact_get_id (remote_contact);
949 status = empathy_contact_get_presence_message (remote_contact);
951 else
953 id = name;
956 if (empathy_chat_is_sms_channel (chat))
957 append_markup_printf (tooltip, "%s ", _("SMS:"));
959 append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
960 id, tp_account_get_display_name (account));
962 if (nb_sending > 0)
964 char *tmp = g_strdup_printf (
965 ngettext ("Sending %d message",
966 "Sending %d messages",
967 nb_sending),
968 nb_sending);
970 g_string_append (tooltip, "\n");
971 g_string_append (tooltip, tmp);
973 gtk_widget_set_tooltip_text (sending_spinner, tmp);
974 g_free (tmp);
977 if (!TPAW_STR_EMPTY (status))
978 append_markup_printf (tooltip, "\n<i>%s</i>", status);
980 if (!TPAW_STR_EMPTY (subject))
981 append_markup_printf (tooltip, "\n<b>%s</b> %s",
982 _("Topic:"), subject);
984 if (remote_contact && empathy_chat_is_composing (chat))
985 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
987 if (remote_contact != NULL)
989 const gchar * const *types;
991 types = empathy_contact_get_client_types (remote_contact);
992 if (empathy_client_types_contains_mobile_device ((GStrv) types))
994 /* I'm on a mobile device ! */
995 gchar *tmp = name;
997 name = g_strdup_printf ("☎ %s", name);
998 g_free (tmp);
1002 markup = g_string_free (tooltip, FALSE);
1003 widget = g_object_get_data (G_OBJECT (chat),
1004 "chat-window-tab-tooltip-widget");
1005 gtk_widget_set_tooltip_markup (widget, markup);
1007 widget = g_object_get_data (G_OBJECT (chat),
1008 "chat-window-menu-tooltip-widget");
1009 gtk_widget_set_tooltip_markup (widget, markup);
1010 g_free (markup);
1012 /* Update tab and menu label */
1013 if (empathy_chat_is_highlighted (chat))
1015 markup = g_markup_printf_escaped (
1016 "<span color=\"red\" weight=\"bold\">%s</span>",
1017 name);
1019 else
1021 markup = g_markup_escape_text (name, -1);
1024 widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1025 gtk_label_set_markup (GTK_LABEL (widget), markup);
1026 widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1027 gtk_label_set_markup (GTK_LABEL (widget), markup);
1028 g_free (markup);
1030 /* Update the window if it's the current chat */
1031 if (self->priv->current_chat == chat)
1032 chat_window_update (self, update_contact_menu);
1034 g_free (name);
1037 static void
1038 chat_window_update_chat_tab (EmpathyChat *chat)
1040 chat_window_update_chat_tab_full (chat, TRUE);
1043 static void
1044 chat_window_chat_notify_cb (EmpathyChat *chat)
1046 EmpathyChatWindow *window;
1047 EmpathyContact *old_remote_contact;
1048 EmpathyContact *remote_contact = NULL;
1050 old_remote_contact = g_object_get_data (G_OBJECT (chat),
1051 "chat-window-remote-contact");
1052 remote_contact = empathy_chat_get_remote_contact (chat);
1054 if (old_remote_contact != remote_contact)
1056 /* The remote-contact associated with the chat changed, we need
1057 * to keep track of any change of that contact and update the
1058 * window each time. */
1059 if (remote_contact)
1060 g_signal_connect_swapped (remote_contact, "notify",
1061 G_CALLBACK (chat_window_update_chat_tab), chat);
1063 if (old_remote_contact)
1064 g_signal_handlers_disconnect_by_func (old_remote_contact,
1065 chat_window_update_chat_tab, chat);
1067 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1068 g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1071 chat_window_update_chat_tab (chat);
1073 window = chat_window_find_chat (chat);
1074 if (window != NULL)
1075 chat_window_update (window, FALSE);
1078 static void
1079 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1080 EmpathySmiley *smiley,
1081 gpointer user_data)
1083 EmpathyChatWindow *self = user_data;
1084 EmpathyChat *chat;
1085 GtkTextBuffer *buffer;
1087 chat = self->priv->current_chat;
1088 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1090 empathy_chat_insert_smiley (buffer, smiley);
1093 static void
1094 chat_window_conv_activate_cb (GtkAction *action,
1095 EmpathyChatWindow *self)
1097 gboolean is_room;
1098 gboolean active;
1099 EmpathyContact *remote_contact = NULL;
1100 gboolean disconnected;
1102 /* Favorite room menu */
1103 is_room = empathy_chat_is_room (self->priv->current_chat);
1104 if (is_room)
1106 const gchar *room;
1107 TpAccount *account;
1108 gboolean found = FALSE;
1109 EmpathyChatroom *chatroom;
1111 room = empathy_chat_get_id (self->priv->current_chat);
1112 account = empathy_chat_get_account (self->priv->current_chat);
1113 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1114 account, room);
1116 if (chatroom != NULL)
1117 found = empathy_chatroom_is_favorite (chatroom);
1119 DEBUG ("This room %s favorite", found ? "is" : "is not");
1120 gtk_toggle_action_set_active (
1121 GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1123 if (chatroom != NULL)
1124 found = empathy_chatroom_is_always_urgent (chatroom);
1126 gtk_toggle_action_set_active (
1127 GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1130 gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1131 gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1133 /* Show contacts menu */
1134 g_object_get (self->priv->current_chat,
1135 "remote-contact", &remote_contact,
1136 "show-contacts", &active,
1137 NULL);
1139 if (remote_contact == NULL)
1141 gtk_toggle_action_set_active (
1142 GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1145 /* Menu-items to be visible for MUCs only */
1146 gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1147 (remote_contact == NULL));
1149 disconnected = (empathy_chat_get_tp_chat (self->priv->current_chat) == NULL);
1150 if (disconnected)
1152 gtk_action_set_visible (self->priv->menu_conv_join_chat, TRUE);
1153 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1155 else
1157 TpChannel *channel = NULL;
1158 TpContact *self_contact = NULL;
1159 TpHandle self_handle = 0;
1161 channel = (TpChannel *) (empathy_chat_get_tp_chat (
1162 self->priv->current_chat));
1163 self_contact = tp_channel_group_get_self_contact (channel);
1164 if (self_contact == NULL)
1166 /* The channel may not be a group */
1167 gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1169 else
1171 self_handle = tp_contact_get_handle (self_contact);
1172 /* There is sometimes a lag between the members-changed signal
1173 emitted on tp-chat and invalidated signal being emitted on the channel.
1174 Leave Chat menu-item should be sensitive only till our self-handle is
1175 a part of channel-members */
1176 gtk_action_set_visible (self->priv->menu_conv_leave_chat,
1177 self_handle != 0);
1180 /* Join Chat is insensitive for a connected chat */
1181 gtk_action_set_visible (self->priv->menu_conv_join_chat, FALSE);
1184 if (remote_contact != NULL)
1185 g_object_unref (remote_contact);
1188 static void
1189 chat_window_clear_activate_cb (GtkAction *action,
1190 EmpathyChatWindow *self)
1192 empathy_chat_clear (self->priv->current_chat);
1195 static void
1196 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1197 EmpathyChatWindow *self)
1199 gboolean active;
1200 TpAccount *account;
1201 gchar *name;
1202 const gchar *room;
1203 EmpathyChatroom *chatroom;
1205 active = gtk_toggle_action_get_active (toggle_action);
1206 account = empathy_chat_get_account (self->priv->current_chat);
1207 room = empathy_chat_get_id (self->priv->current_chat);
1208 name = empathy_chat_dup_name (self->priv->current_chat);
1210 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1211 account, room, name);
1213 empathy_chatroom_set_favorite (chatroom, active);
1214 g_object_unref (chatroom);
1215 g_free (name);
1218 static void
1219 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1220 EmpathyChatWindow *self)
1222 gboolean active;
1223 TpAccount *account;
1224 gchar *name;
1225 const gchar *room;
1226 EmpathyChatroom *chatroom;
1228 active = gtk_toggle_action_get_active (toggle_action);
1229 account = empathy_chat_get_account (self->priv->current_chat);
1230 room = empathy_chat_get_id (self->priv->current_chat);
1231 name = empathy_chat_dup_name (self->priv->current_chat);
1233 chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1234 account, room, name);
1236 empathy_chatroom_set_always_urgent (chatroom, active);
1237 g_object_unref (chatroom);
1238 g_free (name);
1241 static void
1242 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1243 EmpathyChatWindow *self)
1245 gboolean active;
1247 active = gtk_toggle_action_get_active (toggle_action);
1249 empathy_chat_set_show_contacts (self->priv->current_chat, active);
1252 static void
1253 chat_window_invite_participant_activate_cb (GtkAction *action,
1254 EmpathyChatWindow *self)
1256 GtkWidget *dialog;
1257 EmpathyTpChat *tp_chat;
1258 int response;
1260 g_return_if_fail (self->priv->current_chat != NULL);
1262 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1264 dialog = empathy_invite_participant_dialog_new (
1265 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), tp_chat);
1267 gtk_widget_show (dialog);
1269 response = gtk_dialog_run (GTK_DIALOG (dialog));
1271 if (response == GTK_RESPONSE_ACCEPT)
1273 TpContact *tp_contact;
1274 EmpathyContact *contact;
1276 tp_contact = empathy_invite_participant_dialog_get_selected (
1277 EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1278 if (tp_contact == NULL)
1279 goto out;
1281 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1283 empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1285 g_object_unref (contact);
1288 out:
1289 gtk_widget_destroy (dialog);
1292 static void
1293 chat_window_join_chat_activate_cb (GtkAction *action,
1294 EmpathyChatWindow *self)
1296 g_return_if_fail (self->priv->current_chat != NULL);
1298 empathy_chat_join_muc (self->priv->current_chat,
1299 empathy_chat_get_id (self->priv->current_chat));
1302 static void
1303 chat_window_leave_chat_activate_cb (GtkAction *action,
1304 EmpathyChatWindow *self)
1306 EmpathyTpChat * tp_chat;
1308 g_return_if_fail (self->priv->current_chat != NULL);
1310 tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1311 if (tp_chat != NULL)
1312 empathy_tp_chat_leave (tp_chat, "");
1315 static void
1316 chat_window_close_activate_cb (GtkAction *action,
1317 EmpathyChatWindow *self)
1319 g_return_if_fail (self->priv->current_chat != NULL);
1321 maybe_close_chat (self, self->priv->current_chat);
1324 static void
1325 theme_adium_can_copy_cb (EmpathyThemeAdium *view,
1326 GAsyncResult *result,
1327 EmpathyChatWindow *self)
1329 gtk_action_set_sensitive (self->priv->menu_edit_copy,
1330 empathy_theme_adium_can_copy_finish (view, result, NULL));
1333 static void
1334 chat_window_edit_activate_cb (GtkAction *action,
1335 EmpathyChatWindow *self)
1337 GtkClipboard *clipboard;
1338 GtkTextBuffer *buffer;
1339 gboolean text_available;
1341 g_return_if_fail (self->priv->current_chat != NULL);
1343 if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1345 gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1346 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1347 gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1348 return;
1351 buffer = gtk_text_view_get_buffer (
1352 GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1354 if (gtk_text_buffer_get_has_selection (buffer))
1356 gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1357 gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1359 else
1361 empathy_theme_adium_can_copy (self->priv->current_chat->view, NULL,
1362 (GAsyncReadyCallback)theme_adium_can_copy_cb, self);
1363 gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1366 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1367 text_available = gtk_clipboard_wait_is_text_available (clipboard);
1368 gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1371 static void
1372 chat_window_cut_activate_cb (GtkAction *action,
1373 EmpathyChatWindow *self)
1375 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1377 empathy_chat_cut (self->priv->current_chat);
1380 static void
1381 chat_window_copy_activate_cb (GtkAction *action,
1382 EmpathyChatWindow *self)
1384 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1386 empathy_chat_copy (self->priv->current_chat);
1389 static void
1390 chat_window_paste_activate_cb (GtkAction *action,
1391 EmpathyChatWindow *self)
1393 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1395 empathy_chat_paste (self->priv->current_chat);
1398 static void
1399 chat_window_find_activate_cb (GtkAction *action,
1400 EmpathyChatWindow *self)
1402 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1404 empathy_chat_find (self->priv->current_chat);
1407 static void
1408 chat_window_tabs_next_activate_cb (GtkAction *action,
1409 EmpathyChatWindow *self)
1411 gint index_, numPages;
1412 gboolean wrap_around;
1414 g_object_get (gtk_settings_get_default (),
1415 "gtk-keynav-wrap-around", &wrap_around,
1416 NULL);
1418 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1419 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1421 if (index_ == (numPages - 1) && wrap_around)
1423 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1424 return;
1427 gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1430 static void
1431 chat_window_tabs_previous_activate_cb (GtkAction *action,
1432 EmpathyChatWindow *self)
1434 gint index_, numPages;
1435 gboolean wrap_around;
1437 g_object_get (gtk_settings_get_default (),
1438 "gtk-keynav-wrap-around", &wrap_around,
1439 NULL);
1441 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1442 numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1444 if (index_ <= 0 && wrap_around)
1446 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1447 numPages - 1);
1448 return;
1451 gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1454 void
1455 empathy_chat_window_next_tab (EmpathyChatWindow *self)
1457 chat_window_tabs_next_activate_cb (NULL, self);
1460 void
1461 empathy_chat_window_prev_tab (EmpathyChatWindow *self)
1463 chat_window_tabs_previous_activate_cb (NULL, self);
1467 static void
1468 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1469 EmpathyChatWindow *self)
1471 empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1472 empathy_get_current_action_time ());
1475 static void
1476 chat_window_tabs_left_activate_cb (GtkAction *action,
1477 EmpathyChatWindow *self)
1479 EmpathyChat *chat;
1480 gint index_, num_pages;
1482 chat = self->priv->current_chat;
1483 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1484 if (index_ <= 0)
1485 return;
1487 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1488 index_ - 1);
1490 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1491 chat_window_menu_context_update (self, num_pages);
1494 static void
1495 chat_window_tabs_right_activate_cb (GtkAction *action,
1496 EmpathyChatWindow *self)
1498 EmpathyChat *chat;
1499 gint index_, num_pages;
1501 chat = self->priv->current_chat;
1502 index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1504 gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1505 index_ + 1);
1507 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1508 chat_window_menu_context_update (self, num_pages);
1511 EmpathyChatWindow *
1512 empathy_chat_window_new (void)
1514 return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1515 NULL);
1518 static void
1519 chat_window_detach_activate_cb (GtkAction *action,
1520 EmpathyChatWindow *self)
1522 EmpathyChatWindow *new_window;
1523 EmpathyChat *chat;
1525 chat = self->priv->current_chat;
1526 new_window = empathy_chat_window_new ();
1528 empathy_chat_window_move_chat (self, new_window, chat);
1530 gtk_widget_show (GTK_WIDGET (new_window));
1533 static void
1534 chat_window_help_contents_activate_cb (GtkAction *action,
1535 EmpathyChatWindow *self)
1537 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1540 static void
1541 chat_window_help_about_activate_cb (GtkAction *action,
1542 EmpathyChatWindow *self)
1544 empathy_about_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))));
1547 static gboolean
1548 chat_window_delete_event_cb (GtkWidget *dialog,
1549 GdkEvent *event,
1550 EmpathyChatWindow *self)
1552 EmpathyChat *chat = NULL;
1553 guint n_rooms = 0;
1554 GList *l;
1556 DEBUG ("Delete event received");
1558 for (l = self->priv->chats; l != NULL; l = l->next)
1560 if (chat_needs_close_confirmation (l->data))
1562 chat = l->data;
1563 n_rooms++;
1567 if (n_rooms > 0)
1569 confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1571 else
1573 remove_all_chats (self);
1576 return TRUE;
1579 static void
1580 chat_window_composing_cb (EmpathyChat *chat,
1581 gboolean is_composing,
1582 EmpathyChatWindow *self)
1584 chat_window_update_chat_tab (chat);
1587 static void
1588 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1589 gboolean urgent)
1591 //gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1594 static void
1595 chat_window_notification_closed_cb (NotifyNotification *notify,
1596 EmpathyChatWindow *self)
1598 g_object_unref (notify);
1599 if (self->priv->notification == notify)
1600 self->priv->notification = NULL;
1603 static void
1604 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1605 EmpathyMessage *message,
1606 EmpathyChat *chat)
1608 EmpathyContact *sender;
1609 const gchar *header;
1610 char *escaped;
1611 const char *body;
1612 GdkPixbuf *pixbuf;
1613 gboolean res, has_x_canonical_append;
1614 NotifyNotification *notification = self->priv->notification;
1616 if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1617 return;
1619 res = g_settings_get_boolean (self->priv->gsettings_notif,
1620 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1622 if (!res)
1623 return;
1625 sender = empathy_message_get_sender (message);
1626 header = empathy_contact_get_alias (sender);
1627 body = empathy_message_get_body (message);
1628 escaped = g_markup_escape_text (body, -1);
1630 has_x_canonical_append = empathy_notify_manager_has_capability (
1631 self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1633 if (notification != NULL && !has_x_canonical_append)
1635 /* if the notification server supports x-canonical-append, it is
1636 better to not use notify_notification_update to avoid
1637 overwriting the current notification message */
1638 notify_notification_update (notification,
1639 header, escaped, NULL);
1641 else
1643 /* if the notification server supports x-canonical-append,
1644 the hint will be added, so that the message from the
1645 just created notification will be automatically appended
1646 to an existing notification with the same title.
1647 In this way the previous message will not be lost: the new
1648 message will appear below it, in the same notification */
1649 const gchar *category = empathy_chat_is_room (chat)
1650 ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1651 : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1653 notification = empathy_notify_manager_create_notification (header,
1654 escaped, NULL);
1656 if (self->priv->notification == NULL)
1657 self->priv->notification = notification;
1659 tp_g_signal_connect_object (notification, "closed",
1660 G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1662 if (has_x_canonical_append)
1664 /* We have to set a not empty string to keep libnotify happy */
1665 notify_notification_set_hint_string (notification,
1666 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1669 notify_notification_set_hint (notification,
1670 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1673 pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1674 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1676 if (pixbuf != NULL)
1678 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1679 g_object_unref (pixbuf);
1682 notify_notification_show (notification, NULL);
1684 g_free (escaped);
1687 static gboolean
1688 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1690 gboolean has_focus;
1692 g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1694 g_object_get ( gtk_widget_get_toplevel (GTK_WIDGET (self)), "has-toplevel-focus", &has_focus, NULL);
1696 return has_focus;
1699 static void
1700 chat_window_new_message_cb (EmpathyChat *chat,
1701 EmpathyMessage *message,
1702 gboolean pending,
1703 gboolean should_highlight,
1704 EmpathyChatWindow *self)
1706 gboolean has_focus;
1707 gboolean needs_urgency;
1708 EmpathyContact *sender;
1710 has_focus = empathy_chat_window_has_focus (self);
1712 /* - if we're the sender, we play the sound if it's specified in the
1713 * preferences and we're not away.
1714 * - if we receive a message, we play the sound if it's specified in the
1715 * preferences and the window does not have focus on the chat receiving
1716 * the message.
1719 sender = empathy_message_get_sender (message);
1721 if (empathy_contact_is_user (sender))
1723 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1724 EMPATHY_SOUND_MESSAGE_OUTGOING);
1725 return;
1728 if (has_focus && self->priv->current_chat == chat)
1730 /* window and tab are focused so consider the message to be read */
1732 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1733 empathy_chat_messages_read (chat);
1734 return;
1737 /* Update the chat tab if this is the first unread message */
1738 if (empathy_chat_get_nb_unread_messages (chat) == 1)
1740 chat_window_update_chat_tab (chat);
1743 /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1744 * If empathy_chat_get_remote_contact () returns NULL, that means it's
1745 * an unamed MUC (msn-like).
1746 * In case of a MUC, we set urgency if either:
1747 * a) the chatroom's always_urgent property is TRUE
1748 * b) the message contains our alias
1750 if (empathy_chat_is_room (chat))
1752 TpAccount *account;
1753 const gchar *room;
1754 EmpathyChatroom *chatroom;
1756 account = empathy_chat_get_account (chat);
1757 room = empathy_chat_get_id (chat);
1759 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1760 account, room);
1762 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1763 needs_urgency = TRUE;
1764 else
1765 needs_urgency = should_highlight;
1767 else
1769 needs_urgency = TRUE;
1772 if (needs_urgency)
1774 if (!has_focus)
1775 chat_window_set_urgency_hint (self, TRUE);
1777 /* Pending messages have already been displayed and notified in the
1778 * approver, so we don't display a notification and play a sound
1779 * for those */
1780 if (!pending)
1782 empathy_sound_manager_play (self->priv->sound_mgr,
1783 GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1785 chat_window_show_or_update_notification (self, message, chat);
1789 /* update the number of unread messages and the window icon */
1790 chat_window_title_update (self);
1791 chat_window_icon_update (self, TRUE);
1794 static void
1795 chat_window_command_part (EmpathyChat *chat,
1796 GStrv strv)
1798 EmpathyChat *chat_to_be_parted;
1799 EmpathyTpChat *tp_chat = NULL;
1801 if (strv[1] == NULL)
1803 /* No chatroom ID specified */
1804 tp_chat = empathy_chat_get_tp_chat (chat);
1806 if (tp_chat)
1807 empathy_tp_chat_leave (tp_chat, "");
1809 return;
1812 chat_to_be_parted = empathy_chat_window_find_chat (
1813 empathy_chat_get_account (chat), strv[1], FALSE);
1815 if (chat_to_be_parted != NULL)
1817 /* Found a chatroom matching the specified ID */
1818 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1820 if (tp_chat)
1821 empathy_tp_chat_leave (tp_chat, strv[2]);
1823 else
1825 gchar *message;
1827 /* Going by the syntax of PART command:
1829 * /PART [<chatroom-ID>] [<reason>]
1831 * Chatroom-ID is not a must to specify a reason.
1832 * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1833 * MUC then the current chatroom should be parted and srtv[1] should
1834 * be treated as part of the optional part-message. */
1835 message = g_strconcat (strv[1], " ", strv[2], NULL);
1836 tp_chat = empathy_chat_get_tp_chat (chat);
1838 if (tp_chat)
1839 empathy_tp_chat_leave (tp_chat, message);
1841 g_free (message);
1845 static GtkNotebook *
1846 notebook_create_window_cb (GtkNotebook *source,
1847 GtkWidget *page,
1848 gint x,
1849 gint y,
1850 gpointer user_data)
1852 EmpathyChatWindow *window, *new_window;
1853 EmpathyChat *chat;
1855 chat = EMPATHY_CHAT (page);
1856 window = chat_window_find_chat (chat);
1858 new_window = empathy_chat_window_new ();
1860 DEBUG ("Detach hook called");
1862 empathy_chat_window_move_chat (window, new_window, chat);
1864 gtk_widget_show (GTK_WIDGET (new_window));
1865 //gtk_window_move (GTK_WINDOW (new_window), x, y);
1867 return NULL;
1870 static void
1871 chat_window_page_switched_cb (GtkNotebook *notebook,
1872 GtkWidget *child,
1873 gint page_num,
1874 EmpathyChatWindow *self)
1876 EmpathyChat *chat = EMPATHY_CHAT (child);
1878 DEBUG ("Page switched");
1880 if (self->priv->page_added)
1882 self->priv->page_added = FALSE;
1883 empathy_chat_scroll_down (chat);
1885 else if (self->priv->current_chat == chat)
1887 return;
1890 self->priv->current_chat = chat;
1891 empathy_chat_messages_read (chat);
1893 chat_window_update_chat_tab (chat);
1896 static void
1897 chat_window_page_added_cb (GtkNotebook *notebook,
1898 GtkWidget *child,
1899 guint page_num,
1900 EmpathyChatWindow *self)
1902 EmpathyChat *chat;
1904 /* If we just received DND to the same window, we don't want
1905 * to do anything here like removing the tab and then readding
1906 * it, so we return here and in "page-added".
1908 if (self->priv->dnd_same_window)
1910 DEBUG ("Page added (back to the same window)");
1911 self->priv->dnd_same_window = FALSE;
1912 return;
1915 DEBUG ("Page added");
1917 /* Get chat object */
1918 chat = EMPATHY_CHAT (child);
1920 /* Connect chat signals for this window */
1921 g_signal_connect (chat, "composing",
1922 G_CALLBACK (chat_window_composing_cb), self);
1923 g_signal_connect (chat, "new-message",
1924 G_CALLBACK (chat_window_new_message_cb), self);
1925 g_signal_connect (chat, "part-command-entered",
1926 G_CALLBACK (chat_window_command_part), NULL);
1927 g_signal_connect (chat, "notify::tp-chat",
1928 G_CALLBACK (chat_window_update_chat_tab), self);
1930 /* Set flag so we know to perform some special operations on
1931 * switch page due to the new page being added.
1933 self->priv->page_added = TRUE;
1935 /* Get list of chats up to date */
1936 self->priv->chats = g_list_append (self->priv->chats, chat);
1938 chat_window_update_chat_tab (chat);
1941 static void
1942 chat_window_page_removed_cb (GtkNotebook *notebook,
1943 GtkWidget *child,
1944 guint page_num,
1945 EmpathyChatWindow *self)
1947 EmpathyChat *chat;
1949 /* If we just received DND to the same window, we don't want
1950 * to do anything here like removing the tab and then readding
1951 * it, so we return here and in "page-added".
1953 if (self->priv->dnd_same_window)
1955 DEBUG ("Page removed (and will be readded to same window)");
1956 return;
1959 DEBUG ("Page removed");
1961 /* Get chat object */
1962 chat = EMPATHY_CHAT (child);
1964 /* Disconnect all signal handlers for this chat and this window */
1965 g_signal_handlers_disconnect_by_func (chat,
1966 G_CALLBACK (chat_window_composing_cb), self);
1967 g_signal_handlers_disconnect_by_func (chat,
1968 G_CALLBACK (chat_window_new_message_cb), self);
1969 g_signal_handlers_disconnect_by_func (chat,
1970 G_CALLBACK (chat_window_update_chat_tab), self);
1972 /* Keep list of chats up to date */
1973 self->priv->chats = g_list_remove (self->priv->chats, chat);
1974 empathy_chat_messages_read (chat);
1976 if (self->priv->chats == NULL)
1978 gtk_widget_destroy (GTK_WIDGET (self));
1980 else
1982 chat_window_update (self, TRUE);
1986 static gboolean
1987 chat_window_focus_in_event_cb (GtkWidget *widget,
1988 GdkEvent *event,
1989 EmpathyChatWindow *self)
1991 if (self->priv->current_chat == NULL) {
1992 return FALSE;
1994 empathy_chat_messages_read (self->priv->current_chat);
1996 chat_window_set_urgency_hint (self, FALSE);
1998 /* Update the title, since we now mark all unread messages as read. */
1999 chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
2001 return FALSE;
2004 static void
2005 contacts_loaded_cb (EmpathyIndividualManager *mgr,
2006 EmpathyChatWindow *self)
2008 chat_window_contact_menu_update (self);
2011 static gboolean
2012 chat_window_focus_out_event_cb (GtkWidget *widget,
2013 GdkEvent *event,
2014 EmpathyChatWindow *self)
2016 if (self->priv->individual_mgr != NULL)
2017 return FALSE;
2019 /* Keep the individual manager alive so we won't fetch everything from Folks
2020 * each time we need to use it. Loading FolksAggregator can takes quite a
2021 * while (if user has a huge LDAP abook for example) and it blocks
2022 * the mainloop during most of this loading. We workaround this by loading
2023 * it when the chat window has been unfocused and so, hopefully, not impact
2024 * the reactivity of the chat window too much.
2026 * The individual manager (and so Folks) is needed to know to which
2027 * FolksIndividual a TpContact belongs, including:
2028 * - empathy_chat_get_contact_menu: to list all the personas of the contact
2029 * - empathy_display_individual_info: to invoke gnome-contacts with the
2030 * FolksIndividual.id of the contact
2031 * - drag_data_received_individual_id: to find the individual associated
2032 * with the ID we received from the DnD in order to invite him.
2034 self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
2036 if (!empathy_individual_manager_get_contacts_loaded (
2037 self->priv->individual_mgr))
2039 /* We want to update the contact menu when Folks is loaded so we can
2040 * list all the personas of the contact. */
2041 tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
2042 G_CALLBACK (contacts_loaded_cb), self, 0);
2045 g_object_notify (G_OBJECT (self), "individual-manager");
2047 return FALSE;
2050 static gboolean
2051 chat_window_drag_drop (GtkWidget *widget,
2052 GdkDragContext *context,
2053 int x,
2054 int y,
2055 guint time_,
2056 EmpathyChatWindow *self)
2058 GdkAtom target;
2060 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2061 if (target == GDK_NONE)
2062 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2064 if (target != GDK_NONE)
2066 gtk_drag_get_data (widget, context, target, time_);
2067 return TRUE;
2070 return FALSE;
2073 static gboolean
2074 chat_window_drag_motion (GtkWidget *widget,
2075 GdkDragContext *context,
2076 int x,
2077 int y,
2078 guint time_,
2079 EmpathyChatWindow *self)
2081 GdkAtom target;
2083 target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2085 if (target != GDK_NONE)
2087 /* This is a file drag. Ensure the contact is online and set the
2088 drag type to COPY. Note that it's possible that the tab will
2089 be switched by GTK+ after a timeout from drag_motion without
2090 getting another drag_motion to disable the drop. You have
2091 to hold your mouse really still.
2093 EmpathyContact *contact;
2095 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2097 /* contact is NULL for multi-user chats. We don't do
2098 * file transfers to MUCs. We also don't send files
2099 * to offline contacts or contacts that don't support
2100 * file transfer.
2102 if ((contact == NULL) || !empathy_contact_is_online (contact))
2104 gdk_drag_status (context, 0, time_);
2105 return FALSE;
2108 if (!(empathy_contact_get_capabilities (contact)
2109 & EMPATHY_CAPABILITIES_FT))
2111 gdk_drag_status (context, 0, time_);
2112 return FALSE;
2115 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2116 return TRUE;
2119 target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2120 if (target != GDK_NONE)
2122 /* This is a drag of a contact from a contact list. Set to COPY.
2123 FIXME: If this drag is to a MUC window, it invites the user.
2124 Otherwise, it opens a chat. Should we use a different drag
2125 type for invites? Should we allow ASK?
2127 gdk_drag_status (context, GDK_ACTION_COPY, time_);
2128 return TRUE;
2131 return FALSE;
2134 static void
2135 drag_data_received_individual_id (EmpathyChatWindow *self,
2136 GtkWidget *widget,
2137 GdkDragContext *context,
2138 int x,
2139 int y,
2140 GtkSelectionData *selection,
2141 guint info,
2142 guint time_)
2144 const gchar *id;
2145 FolksIndividual *individual;
2146 EmpathyTpChat *chat;
2147 TpContact *tp_contact;
2148 TpConnection *conn;
2149 EmpathyContact *contact;
2151 id = (const gchar *) gtk_selection_data_get_data (selection);
2153 DEBUG ("DND invididual %s", id);
2155 if (self->priv->current_chat == NULL)
2156 goto out;
2158 chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2159 if (chat == NULL)
2160 goto out;
2162 if (!empathy_tp_chat_can_add_contact (chat))
2164 DEBUG ("Can't invite contact to %s",
2165 tp_proxy_get_object_path (chat));
2166 goto out;
2169 if (self->priv->individual_mgr == NULL)
2170 /* Not likely as we have to focus out the chat window in order to start
2171 * the DnD but best to be safe. */
2172 goto out;
2174 individual = empathy_individual_manager_lookup_member (
2175 self->priv->individual_mgr, id);
2176 if (individual == NULL)
2178 DEBUG ("Failed to find individual %s", id);
2179 goto out;
2182 conn = tp_channel_get_connection ((TpChannel *) chat);
2183 tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2184 if (tp_contact == NULL)
2186 DEBUG ("Can't find a TpContact on connection %s for %s",
2187 tp_proxy_get_object_path (conn), id);
2188 goto out;
2191 DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2192 tp_channel_get_identifier ((TpChannel *) chat));
2194 contact = empathy_contact_dup_from_tp_contact (tp_contact);
2195 empathy_tp_chat_add (chat, contact, NULL);
2196 g_object_unref (contact);
2198 out:
2199 gtk_drag_finish (context, TRUE, FALSE, time_);
2202 static void
2203 chat_window_drag_data_received (GtkWidget *widget,
2204 GdkDragContext *context,
2205 int x,
2206 int y,
2207 GtkSelectionData *selection,
2208 guint info,
2209 guint time_,
2210 EmpathyChatWindow *self)
2212 if (info == DND_DRAG_TYPE_CONTACT_ID)
2214 EmpathyChat *chat = NULL;
2215 EmpathyChatWindow *old_window;
2216 TpAccount *account = NULL;
2217 EmpathyClientFactory *factory;
2218 const gchar *id;
2219 gchar **strv;
2220 const gchar *account_id;
2221 const gchar *contact_id;
2223 id = (const gchar*) gtk_selection_data_get_data (selection);
2225 factory = empathy_client_factory_dup ();
2227 DEBUG ("DND contact from roster with id:'%s'", id);
2229 strv = g_strsplit (id, ":", 2);
2230 if (g_strv_length (strv) == 2)
2232 account_id = strv[0];
2233 contact_id = strv[1];
2235 account = tp_simple_client_factory_ensure_account (
2236 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2238 g_object_unref (factory);
2239 if (account != NULL)
2240 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2243 if (account == NULL)
2245 g_strfreev (strv);
2246 gtk_drag_finish (context, FALSE, FALSE, time_);
2247 return;
2250 if (!chat)
2252 empathy_chat_with_contact_id (account, contact_id,
2253 empathy_get_current_action_time (), NULL, NULL);
2255 g_strfreev (strv);
2256 return;
2259 g_strfreev (strv);
2261 old_window = chat_window_find_chat (chat);
2262 if (old_window)
2264 if (old_window == self)
2266 gtk_drag_finish (context, TRUE, FALSE, time_);
2267 return;
2270 empathy_chat_window_move_chat (old_window, self, chat);
2272 else
2274 empathy_chat_window_add_chat (self, chat);
2277 /* Added to take care of any outstanding chat events */
2278 empathy_chat_window_present_chat (chat,
2279 TP_USER_ACTION_TIME_NOT_USER_ACTION);
2281 /* We should return TRUE to remove the data when doing
2282 * GDK_ACTION_MOVE, but we don't here otherwise it has
2283 * weird consequences, and we handle that internally
2284 * anyway with add_chat () and remove_chat ().
2286 gtk_drag_finish (context, TRUE, FALSE, time_);
2288 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2290 drag_data_received_individual_id (self, widget, context, x, y,
2291 selection, info, time_);
2293 else if (info == DND_DRAG_TYPE_URI_LIST)
2295 EmpathyContact *contact;
2296 const gchar *data;
2298 contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2300 /* contact is NULL when current_chat is a multi-user chat.
2301 * We don't do file transfers to MUCs, so just cancel the drag.
2303 if (contact == NULL)
2305 gtk_drag_finish (context, TRUE, FALSE, time_);
2306 return;
2309 data = (const gchar *) gtk_selection_data_get_data (selection);
2310 empathy_send_file_from_uri_list (contact, data);
2312 gtk_drag_finish (context, TRUE, FALSE, time_);
2314 else if (info == DND_DRAG_TYPE_TAB)
2316 EmpathyChat **chat;
2317 EmpathyChatWindow *old_window = NULL;
2319 DEBUG ("DND tab");
2321 chat = (void *) gtk_selection_data_get_data (selection);
2322 old_window = chat_window_find_chat (*chat);
2324 if (old_window)
2326 self->priv->dnd_same_window = (old_window == self);
2328 DEBUG ("DND tab (within same window: %s)",
2329 self->priv->dnd_same_window ? "Yes" : "No");
2332 else
2334 DEBUG ("DND from unknown source");
2335 gtk_drag_finish (context, FALSE, FALSE, time_);
2339 static void
2340 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2341 guint num_chats_in_manager,
2342 EmpathyChatWindow *self)
2344 gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2345 num_chats_in_manager > 0);
2348 static void
2349 chat_window_finalize (GObject *object)
2351 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2353 DEBUG ("Finalized: %p", object);
2355 g_object_unref (self->priv->ui_manager);
2356 g_object_unref (self->priv->chatroom_manager);
2357 g_object_unref (self->priv->notify_mgr);
2358 g_object_unref (self->priv->gsettings_chat);
2359 g_object_unref (self->priv->gsettings_notif);
2360 g_object_unref (self->priv->gsettings_ui);
2361 g_object_unref (self->priv->sound_mgr);
2362 g_clear_object (&self->priv->individual_mgr);
2364 if (self->priv->notification != NULL)
2366 notify_notification_close (self->priv->notification, NULL);
2367 self->priv->notification = NULL;
2370 if (self->priv->contact_targets)
2371 gtk_target_list_unref (self->priv->contact_targets);
2373 if (self->priv->file_targets)
2374 gtk_target_list_unref (self->priv->file_targets);
2376 if (self->priv->chat_manager)
2378 g_signal_handler_disconnect (self->priv->chat_manager,
2379 self->priv->chat_manager_chats_changed_id);
2380 g_object_unref (self->priv->chat_manager);
2381 self->priv->chat_manager = NULL;
2384 chat_windows = g_list_remove (chat_windows, self);
2386 G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2389 static void
2390 chat_window_get_property (GObject *object,
2391 guint property_id,
2392 GValue *value,
2393 GParamSpec *pspec)
2395 EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2397 switch (property_id)
2399 case PROP_INDIVIDUAL_MGR:
2400 g_value_set_object (value, self->priv->individual_mgr);
2401 default:
2402 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2403 break;
2407 static void
2408 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2410 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2411 GParamSpec *spec;
2413 object_class->get_property = chat_window_get_property;
2414 object_class->finalize = chat_window_finalize;
2416 spec = g_param_spec_object ("individual-manager", "individual-manager",
2417 "EmpathyIndividualManager",
2418 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2419 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2420 g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2422 g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2425 static void
2426 chat_window_chat_new_message_cb (GSimpleAction *action,
2427 GVariant *parameter,
2428 gpointer user_data)
2430 EmpathyChatWindow *self = user_data;
2432 //empathy_new_message_dialog_show (GTK_WINDOW (self));
2436 static void
2437 empathy_chat_window_init (EmpathyChatWindow *self)
2439 GtkBuilder *gui;
2440 GtkAccelGroup *accel_group;
2441 GClosure *closure;
2442 GtkWidget *menu;
2443 GtkWidget *submenu;
2444 guint i;
2445 GtkWidget *chat_vbox;
2446 GtkWidget *main_box;
2447 gchar *filename;
2448 EmpathySmileyManager *smiley_manager;
2450 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2451 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2453 filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2454 gui = tpaw_builder_get_file (filename,
2455 "chat_vbox", &chat_vbox,
2456 "main_box", &main_box,
2457 "ui_manager", &self->priv->ui_manager,
2458 "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2459 "menu_conv_favorite", &self->priv->menu_conv_favorite,
2460 "menu_conv_join_chat", &self->priv->menu_conv_join_chat,
2461 "menu_conv_leave_chat", &self->priv->menu_conv_leave_chat,
2462 "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2463 "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2464 "menu_edit_cut", &self->priv->menu_edit_cut,
2465 "menu_edit_copy", &self->priv->menu_edit_copy,
2466 "menu_edit_paste", &self->priv->menu_edit_paste,
2467 "menu_edit_find", &self->priv->menu_edit_find,
2468 "menu_tabs_next", &self->priv->menu_tabs_next,
2469 "menu_tabs_prev", &self->priv->menu_tabs_prev,
2470 "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2471 "menu_tabs_left", &self->priv->menu_tabs_left,
2472 "menu_tabs_right", &self->priv->menu_tabs_right,
2473 "menu_tabs_detach", &self->priv->menu_tabs_detach,
2474 NULL);
2475 g_free (filename);
2477 tpaw_builder_connect (gui, self,
2478 "menu_conv", "activate", chat_window_conv_activate_cb,
2479 "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2480 "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2481 "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2482 "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2483 "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2484 "menu_conv_join_chat", "activate", chat_window_join_chat_activate_cb,
2485 "menu_conv_leave_chat", "activate", chat_window_leave_chat_activate_cb,
2486 "menu_conv_close", "activate", chat_window_close_activate_cb,
2487 "menu_edit", "activate", chat_window_edit_activate_cb,
2488 "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2489 "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2490 "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2491 "menu_edit_find", "activate", chat_window_find_activate_cb,
2492 "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2493 "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2494 "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2495 "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2496 "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2497 "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2498 "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2499 "menu_help_about", "activate", chat_window_help_about_activate_cb,
2500 NULL);
2502 empathy_set_css_provider (GTK_WIDGET (self));
2504 self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2505 self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2506 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2507 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2509 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2511 self->priv->notebook = gtk_notebook_new ();
2512 //gtk_notebook_set_show_tabs (GTK_NOTEBOOK (self->priv->notebook), FALSE);
2514 g_signal_connect (self->priv->notebook, "create-window",
2515 G_CALLBACK (notebook_create_window_cb), self);
2517 gtk_container_add (GTK_CONTAINER (self), main_box);
2519 gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2520 "EmpathyChatWindow");
2521 gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2522 gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2523 gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2524 gtk_widget_show (self->priv->notebook);
2526 #if 0 /* no top level window yet at this point */
2527 /* Set up accels */
2528 accel_group = gtk_accel_group_new ();
2529 gtk_window_add_accel_group (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), accel_group);
2531 for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2533 closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2534 NULL);
2536 gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2537 closure);
2540 g_object_unref (accel_group);
2541 #endif
2543 /* Set up drag target lists */
2544 self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2545 G_N_ELEMENTS (drag_types_dest_contact));
2547 self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2548 G_N_ELEMENTS (drag_types_dest_file));
2550 /* Set up smiley menu */
2551 smiley_manager = empathy_smiley_manager_dup_singleton ();
2552 submenu = empathy_smiley_menu_new (smiley_manager,
2553 chat_window_insert_smiley_activate_cb, self);
2555 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2556 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2557 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2558 g_object_unref (smiley_manager);
2560 /* Set up signals we can't do with ui file since we may need to
2561 * block/unblock them at some later stage.
2564 g_signal_connect (self, "delete_event",
2565 G_CALLBACK (chat_window_delete_event_cb), self);
2566 g_signal_connect (self, "focus_in_event",
2567 G_CALLBACK (chat_window_focus_in_event_cb), self);
2568 g_signal_connect (self, "focus_out_event",
2569 G_CALLBACK (chat_window_focus_out_event_cb), self);
2570 g_signal_connect_after (self->priv->notebook, "switch_page",
2571 G_CALLBACK (chat_window_page_switched_cb), self);
2572 g_signal_connect (self->priv->notebook, "page_added",
2573 G_CALLBACK (chat_window_page_added_cb), self);
2574 g_signal_connect (self->priv->notebook, "page_removed",
2575 G_CALLBACK (chat_window_page_removed_cb), self);
2577 /* Set up drag and drop */
2578 gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2579 GTK_DEST_DEFAULT_HIGHLIGHT,
2580 drag_types_dest,
2581 G_N_ELEMENTS (drag_types_dest),
2582 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2584 /* connect_after to allow GtkNotebook's built-in tab switching */
2585 g_signal_connect_after (self->priv->notebook, "drag-motion",
2586 G_CALLBACK (chat_window_drag_motion), self);
2587 g_signal_connect (self->priv->notebook, "drag-data-received",
2588 G_CALLBACK (chat_window_drag_data_received), self);
2589 g_signal_connect (self->priv->notebook, "drag-drop",
2590 G_CALLBACK (chat_window_drag_drop), self);
2592 chat_windows = g_list_prepend (chat_windows, self);
2594 /* Set up private details */
2595 self->priv->chats = NULL;
2596 self->priv->current_chat = NULL;
2597 self->priv->notification = NULL;
2599 self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2601 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2602 self->priv->chat_manager_chats_changed_id = g_signal_connect (
2603 self->priv->chat_manager, "closed-chats-changed",
2604 G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2606 chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2607 empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2609 g_object_ref (self->priv->ui_manager);
2610 g_object_unref (gui);
2613 /* Returns the window to open a new tab in if there is a suitable window,
2614 * otherwise, returns NULL indicating that a new window should be added.
2616 static EmpathyChatWindow *
2617 empathy_chat_window_get_default (gboolean room)
2619 GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2620 GList *l;
2621 gboolean separate_windows = TRUE;
2623 separate_windows = g_settings_get_boolean (gsettings,
2624 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2625 separate_windows = FALSE;
2627 g_object_unref (gsettings);
2629 if (separate_windows)
2630 /* Always create a new window */
2631 return NULL;
2633 for (l = chat_windows; l; l = l->next)
2635 EmpathyChatWindow *chat_window;
2636 guint nb_rooms, nb_private;
2638 chat_window = l->data;
2640 #if 0
2641 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2643 /* Skip the window if there aren't any rooms in it */
2644 if (room && nb_rooms == 0)
2645 continue;
2647 /* Skip the window if there aren't any 1-1 chats in it */
2648 if (!room && nb_private == 0)
2649 continue;
2650 #endif
2652 return chat_window;
2655 return NULL;
2658 static void
2659 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2660 EmpathyChat *chat)
2662 GtkWidget *label;
2663 GtkWidget *popup_label;
2664 GtkWidget *child;
2665 GValue value = { 0, };
2667 g_return_if_fail (self != NULL);
2668 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2670 /* Reference the chat object */
2671 g_object_ref (chat);
2673 /* If this window has just been created, position it */
2674 if (self->priv->chats == NULL)
2676 const gchar *name = "chat-window";
2677 gboolean separate_windows;
2679 separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2680 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2681 separate_windows = FALSE;
2683 if (empathy_chat_is_room (chat))
2684 name = "room-window";
2686 if (separate_windows)
2688 gint x, y;
2690 /* Save current position of the window */
2691 //gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2693 /* First bind to the 'generic' name. So new window for which we didn't
2694 * save a geometry yet will have the geometry of the last saved
2695 * window (bgo #601191). */
2696 //empathy_geometry_bind (GTK_WINDOW (self), name);
2698 /* Restore previous position of the window so the newly created window
2699 * won't be in the same position as the latest saved window and so
2700 * completely hide it. */
2701 //gtk_window_move (GTK_WINDOW (self), x, y);
2703 /* Then bind it to the name of the contact/room so we'll save the
2704 * geometry specific to this window */
2705 name = empathy_chat_get_id (chat);
2708 //empathy_geometry_bind (GTK_WINDOW (self), name);
2711 child = GTK_WIDGET (chat);
2712 label = chat_window_create_label (self, chat, TRUE);
2713 popup_label = chat_window_create_label (self, chat, FALSE);
2714 gtk_widget_show (child);
2716 g_signal_connect (chat, "notify::name",
2717 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2718 g_signal_connect (chat, "notify::subject",
2719 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2720 g_signal_connect (chat, "notify::remote-contact",
2721 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2722 g_signal_connect (chat, "notify::sms-channel",
2723 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2724 g_signal_connect (chat, "notify::n-messages-sending",
2725 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2726 g_signal_connect (chat, "notify::nb-unread-messages",
2727 G_CALLBACK (chat_window_chat_notify_cb), NULL);
2728 chat_window_chat_notify_cb (chat);
2730 gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2731 popup_label);
2732 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2733 gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2734 g_value_init (&value, G_TYPE_BOOLEAN);
2735 g_value_set_boolean (&value, TRUE);
2736 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2737 child, "tab-expand" , &value);
2738 gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2739 child, "tab-fill" , &value);
2740 g_value_unset (&value);
2742 DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2745 static void
2746 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2747 EmpathyChat *chat)
2749 gint position;
2750 EmpathyContact *remote_contact;
2751 EmpathyChatManager *chat_manager;
2753 g_return_if_fail (self != NULL);
2754 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2756 g_signal_handlers_disconnect_by_func (chat,
2757 chat_window_chat_notify_cb, NULL);
2759 remote_contact = g_object_get_data (G_OBJECT (chat),
2760 "chat-window-remote-contact");
2762 if (remote_contact)
2764 g_signal_handlers_disconnect_by_func (remote_contact,
2765 chat_window_update_chat_tab, chat);
2768 chat_manager = empathy_chat_manager_dup_singleton ();
2769 empathy_chat_manager_closed_chat (chat_manager, chat);
2770 g_object_unref (chat_manager);
2772 position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2773 GTK_WIDGET (chat));
2774 gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2776 DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2778 g_object_unref (chat);
2781 static void
2782 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2783 EmpathyChatWindow *new_window,
2784 EmpathyChat *chat)
2786 GtkWidget *widget;
2788 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2789 g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2790 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2792 widget = GTK_WIDGET (chat);
2794 DEBUG ("Chat moving with widget:%p (%d references)", widget,
2795 G_OBJECT (widget)->ref_count);
2797 /* We reference here to make sure we don't loose the widget
2798 * and the EmpathyChat object during the move.
2800 g_object_ref (chat);
2801 g_object_ref (widget);
2803 empathy_chat_window_remove_chat (old_window, chat);
2804 empathy_chat_window_add_chat (new_window, chat);
2806 g_object_unref (widget);
2807 g_object_unref (chat);
2810 static void
2811 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2812 EmpathyChat *chat)
2814 gint page_num;
2816 g_return_if_fail (self != NULL);
2817 g_return_if_fail (EMPATHY_IS_CHAT (chat));
2819 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2820 GTK_WIDGET (chat));
2822 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2823 page_num);
2826 EmpathyChat *
2827 empathy_chat_window_find_chat (TpAccount *account,
2828 const gchar *id,
2829 gboolean sms_channel)
2831 GList *l;
2833 g_return_val_if_fail (!TPAW_STR_EMPTY (id), NULL);
2835 for (l = chat_windows; l; l = l->next)
2837 EmpathyChatWindow *window = l->data;
2838 GList *ll;
2840 for (ll = window->priv->chats; ll; ll = ll->next)
2842 EmpathyChat *chat;
2844 chat = ll->data;
2846 if (account == empathy_chat_get_account (chat) &&
2847 !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2848 sms_channel == empathy_chat_is_sms_channel (chat))
2849 return chat;
2853 return NULL;
2856 EmpathyChatWindow *
2857 empathy_chat_window_present_chat (EmpathyChat *chat,
2858 gint64 timestamp)
2860 EmpathyChatWindow *self;
2861 guint32 x_timestamp;
2863 if (chat == NULL) {
2864 /* initial window */
2865 self = empathy_chat_window_new ();
2866 gtk_widget_show (GTK_WIDGET (self));
2867 return self;
2870 g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2872 self = chat_window_find_chat (chat);
2874 /* If the chat has no window, create one */
2875 if (self == NULL)
2877 self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2878 if (!self)
2880 self = empathy_chat_window_new ();
2882 /* we want to display the newly created window even if we
2883 * don't present it */
2884 gtk_widget_show (GTK_WIDGET (self));
2887 empathy_chat_window_add_chat (self, chat);
2890 /* Don't force the window to show itself when it wasn't
2891 * an action by the user
2893 if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2894 return self;
2896 if (x_timestamp != GDK_CURRENT_TIME)
2898 /* Don't present or switch tab if the action was earlier than the
2899 * last actions X time, accounting for overflow and the first ever
2900 * presentation */
2902 if (self->priv->x_user_action_time != 0
2903 && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2904 return self;
2906 self->priv->x_user_action_time = x_timestamp;
2909 empathy_chat_window_switch_to_chat (self, chat);
2911 /* Don't use tpaw_window_present_with_time () which would move the window
2912 * to our current desktop but move to the window's desktop instead. This is
2913 * more coherent with Shell's 'app is ready' notication which moves the view
2914 * to the app desktop rather than moving the app itself. */
2915 empathy_move_to_window_desktop (GTK_WINDOW (gtk_widget_get_toplevel(GTK_WIDGET(self))), x_timestamp);
2917 gtk_widget_grab_focus (chat->input_text_view);
2918 return self;
2921 static void
2922 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2923 guint *nb_rooms,
2924 guint *nb_private)
2926 GList *l;
2927 guint _nb_rooms = 0, _nb_private = 0;
2929 for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2931 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2932 _nb_rooms++;
2933 else
2934 _nb_private++;
2937 if (nb_rooms != NULL)
2938 *nb_rooms = _nb_rooms;
2939 if (nb_private != NULL)
2940 *nb_private = _nb_private;
2943 EmpathyIndividualManager *
2944 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2946 return self->priv->individual_mgr;