/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 2003-2007 Imendio AB
- * Copyright (C) 2007-2008 Collabora Ltd.
+ * Copyright (C) 2007-2010 Collabora Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
#include <glib/gi18n.h>
#include <libnotify/notification.h>
#include <libempathy/empathy-contact.h>
#include <libempathy/empathy-message.h>
#include <libempathy/empathy-chatroom-manager.h>
+#include <libempathy/empathy-gsettings.h>
#include <libempathy/empathy-utils.h>
#include <libempathy/empathy-tp-contact-factory.h>
#include <libempathy/empathy-contact-list.h>
#include <libempathy-gtk/empathy-images.h>
-#include <libempathy-gtk/empathy-conf.h>
#include <libempathy-gtk/empathy-contact-dialogs.h>
#include <libempathy-gtk/empathy-log-window.h>
#include <libempathy-gtk/empathy-geometry.h>
#include <libempathy-gtk/empathy-smiley-manager.h>
-#include <libempathy-gtk/empathy-sound.h>
+#include <libempathy-gtk/empathy-sound-manager.h>
#include <libempathy-gtk/empathy-ui-utils.h>
#include <libempathy-gtk/empathy-notify-manager.h>
+#include "empathy-chat-manager.h"
#include "empathy-chat-window.h"
#include "empathy-about-dialog.h"
#include "empathy-invite-participant-dialog.h"
#define DEBUG_FLAG EMPATHY_DEBUG_CHAT
#include <libempathy/empathy-debug.h>
-typedef struct {
- EmpathyChatWindow *window;
- EmpathyChat *chat;
-} NotificationData;
+/* Macro to compare guint32 X timestamps, while accounting for wrapping around
+ */
+#define X_EARLIER_OR_EQL(t1, t2) \
+ ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2)) \
+ || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
+ )
#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
typedef struct {
GtkWidget *dialog;
GtkWidget *notebook;
NotifyNotification *notification;
- NotificationData *notification_data;
GtkTargetList *contact_targets;
GtkTargetList *file_targets;
+ EmpathyChatManager *chat_manager;
+ gulong chat_manager_chats_changed_id;
+
/* Menu items. */
GtkUIManager *ui_manager;
GtkAction *menu_conv_insert_smiley;
GtkAction *menu_conv_favorite;
+ GtkAction *menu_conv_always_urgent;
GtkAction *menu_conv_toggle_contacts;
GtkAction *menu_edit_cut;
GtkAction *menu_edit_copy;
GtkAction *menu_edit_paste;
+ GtkAction *menu_edit_find;
GtkAction *menu_tabs_next;
GtkAction *menu_tabs_prev;
+ GtkAction *menu_tabs_undo_close_tab;
GtkAction *menu_tabs_left;
GtkAction *menu_tabs_right;
GtkAction *menu_tabs_detach;
+
+ /* Last user action time we acted upon to show a tab */
+ guint32 x_user_action_time;
+
+ GSettings *gsettings_chat;
+ GSettings *gsettings_notif;
+ GSettings *gsettings_ui;
+
+ EmpathySoundManager *sound_mgr;
} EmpathyChatWindowPriv;
static GList *chat_windows = NULL;
static const guint tab_accel_keys[] = {
- GDK_1, GDK_2, GDK_3, GDK_4, GDK_5,
- GDK_6, GDK_7, GDK_8, GDK_9, GDK_0
+ GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
+ GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
};
typedef enum {
{ "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
{ "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
{ "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
+ { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
};
static const GtkTargetEntry drag_types_dest_contact[] = {
};
static const GtkTargetEntry drag_types_dest_file[] = {
+ /* must be first to be prioritized, in order to receive the
+ * note's file path from Tomboy instead of an URI */
+ { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
{ "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
};
static void chat_window_update (EmpathyChatWindow *window);
+static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
+ EmpathyChat *chat);
+
+static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
+ EmpathyChat *chat);
+
+static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
+ EmpathyChatWindow *new_window,
+ EmpathyChat *chat);
+
+static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
+ guint *nb_rooms,
+ guint *nb_private);
+
G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
static void
{
gboolean first_page;
gboolean last_page;
+ gboolean wrap_around;
gboolean is_connected;
gint page_num;
page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
first_page = (page_num == 0);
last_page = (page_num == (num_pages - 1));
+ g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
+ &wrap_around, NULL);
is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
- DEBUG ("Update window : Menu Contexts (Tabs & Conv)");
-
- gtk_action_set_sensitive (priv->menu_tabs_next, TRUE);
- gtk_action_set_sensitive (priv->menu_tabs_prev, TRUE);
+ gtk_action_set_sensitive (priv->menu_tabs_next, (!last_page ||
+ wrap_around));
+ gtk_action_set_sensitive (priv->menu_tabs_prev, (!first_page ||
+ wrap_around));
gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
"/chats_menubar/menu_contact");
orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
- DEBUG ("Update window : Contact Menu");
-
- if (orig_submenu == NULL || !GTK_WIDGET_VISIBLE (orig_submenu)) {
+ if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
submenu = empathy_chat_get_contact_menu (priv->current_chat);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
gtk_widget_show (menu);
} else {
- empathy_signal_connect_weak (orig_submenu,
+ tp_g_signal_connect_object (orig_submenu,
"notify::visible",
(GCallback)_submenu_notify_visible_changed_cb,
- G_OBJECT (window));
+ window, 0);
}
}
{
gchar *name;
- DEBUG ("Update window : Title");
-
name = get_window_title_name (priv);
gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
g_free (name);
n_chats = g_list_length (priv->chats);
- DEBUG ("Update window : Icon");
-
/* Update window icon */
if (priv->chats_new_msg) {
gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
EMPATHY_IMAGE_MESSAGE);
} else {
- empathy_conf_get_bool (empathy_conf_get (),
- EMPATHY_PREFS_CHAT_AVATAR_IN_ICON,
- &avatar_in_icon);
+ avatar_in_icon = g_settings_get_boolean (priv->gsettings_chat,
+ EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
if (n_chats == 1 && avatar_in_icon) {
remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
GtkWidget *chat_close_button;
gint i;
- DEBUG ("Update window : Close Button");
-
if (num_pages == 1) {
chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
chat_close_button = g_object_get_data (G_OBJECT (chat),
num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
- DEBUG ("Update window");
-
/* Update Tab menu */
chat_window_menu_context_update (priv,
num_pages);
DEBUG ("This room %s favorite", found ? "is" : "is not");
gtk_toggle_action_set_active (
GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
+
+ if (chatroom != NULL)
+ found = empathy_chatroom_is_always_urgent (chatroom);
+
+ gtk_toggle_action_set_active (
+ GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
+ found);
}
gtk_action_set_visible (priv->menu_conv_favorite, is_room);
+ gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
/* Show contacts menu */
g_object_get (priv->current_chat,
account = empathy_chat_get_account (priv->current_chat);
room = empathy_chat_get_id (priv->current_chat);
- chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
- account, room);
+ chatroom = empathy_chatroom_manager_ensure_chatroom (
+ priv->chatroom_manager,
+ account,
+ room,
+ empathy_chat_get_name (priv->current_chat));
- if (chatroom == NULL) {
- const gchar *name;
+ empathy_chatroom_set_favorite (chatroom, active);
+ g_object_unref (chatroom);
+}
- name = empathy_chat_get_name (priv->current_chat);
- chatroom = empathy_chatroom_new_full (account, room, name, FALSE);
- empathy_chatroom_manager_add (priv->chatroom_manager, chatroom);
- g_object_unref (chatroom);
- }
+static void
+chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
+ EmpathyChatWindow *window)
+{
+ EmpathyChatWindowPriv *priv = GET_PRIV (window);
+ gboolean active;
+ TpAccount *account;
+ const gchar *room;
+ EmpathyChatroom *chatroom;
- empathy_chatroom_set_favorite (chatroom, active);
+ active = gtk_toggle_action_get_active (toggle_action);
+ account = empathy_chat_get_account (priv->current_chat);
+ room = empathy_chat_get_id (priv->current_chat);
+
+ chatroom = empathy_chatroom_manager_ensure_chatroom (
+ priv->chatroom_manager,
+ account,
+ room,
+ empathy_chat_get_name (priv->current_chat));
+
+ empathy_chatroom_set_always_urgent (chatroom, active);
+ g_object_unref (chatroom);
}
static void
}
static void
-got_contact_cb (EmpathyTpContactFactory *factory,
+got_contact_cb (TpConnection *connection,
EmpathyContact *contact,
const GError *error,
gpointer user_data,
EmpathyTpChat *tp_chat;
TpChannel *channel;
int response;
+ TpAccount *account;
priv = GET_PRIV (window);
tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
channel = empathy_tp_chat_get_channel (tp_chat);
+ account = empathy_chat_get_account (priv->current_chat);
dialog = empathy_invite_participant_dialog_new (
- GTK_WINDOW (priv->dialog));
+ GTK_WINDOW (priv->dialog), account);
gtk_widget_show (dialog);
response = gtk_dialog_run (GTK_DIALOG (dialog));
if (response == GTK_RESPONSE_ACCEPT) {
TpConnection *connection;
- EmpathyTpContactFactory *factory;
const char *id;
id = empathy_contact_selector_dialog_get_selected (
- EMPATHY_CONTACT_SELECTOR_DIALOG (dialog), NULL);
+ EMPATHY_CONTACT_SELECTOR_DIALOG (dialog), NULL, NULL);
if (EMP_STR_EMPTY (id)) goto out;
connection = tp_channel_borrow_connection (channel);
- factory = empathy_tp_contact_factory_dup_singleton (connection);
-
- empathy_tp_contact_factory_get_from_id (factory, id,
+ empathy_tp_contact_factory_get_from_id (connection, id,
got_contact_cb, tp_chat, NULL, NULL);
-
- g_object_unref (factory);
}
out:
empathy_chat_paste (priv->current_chat);
}
+static void
+chat_window_find_activate_cb (GtkAction *action,
+ EmpathyChatWindow *window)
+{
+ EmpathyChatWindowPriv *priv;
+
+ g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
+
+ priv = GET_PRIV (window);
+
+ empathy_chat_find (priv->current_chat);
+}
+
static void
chat_window_tabs_next_activate_cb (GtkAction *action,
EmpathyChatWindow *window)
EmpathyChatWindowPriv *priv;
EmpathyChat *chat;
gint index_, numPages;
+ gboolean wrap_around;
priv = GET_PRIV (window);
+ g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
+ &wrap_around, NULL);
+
chat = priv->current_chat;
index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
- if (index_ == (numPages - 1)) {
+ if (index_ == (numPages - 1) && wrap_around) {
gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
return;
}
EmpathyChatWindowPriv *priv;
EmpathyChat *chat;
gint index_, numPages;
+ gboolean wrap_around;
priv = GET_PRIV (window);
+ g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
+ &wrap_around, NULL);
+
chat = priv->current_chat;
index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
- if (index_ <= 0) {
+ if (index_ <= 0 && wrap_around) {
gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
return;
}
gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
}
+static void
+chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
+ EmpathyChatWindow *window)
+{
+ EmpathyChatWindowPriv *priv = GET_PRIV (window);
+ empathy_chat_manager_undo_closed_chat (priv->chat_manager);
+}
+
static void
chat_window_tabs_left_activate_cb (GtkAction *action,
EmpathyChatWindow *window)
{
EmpathyChatWindowPriv *priv;
EmpathyChat *chat;
- gint index_;
+ gint index_, num_pages;
priv = GET_PRIV (window);
gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
GTK_WIDGET (chat),
index_ - 1);
+
+ num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
+ chat_window_menu_context_update (priv, num_pages);
}
static void
{
EmpathyChatWindowPriv *priv;
EmpathyChat *chat;
- gint index_;
+ gint index_, num_pages;
priv = GET_PRIV (window);
gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
GTK_WIDGET (chat),
index_ + 1);
+
+ num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
+ chat_window_menu_context_update (priv, num_pages);
+}
+
+static EmpathyChatWindow *
+empathy_chat_window_new (void)
+{
+ return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
}
static void
priv = GET_PRIV (window);
- DEBUG ("Turning %s urgency hint", urgent ? "on" : "off");
gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
}
-static void
-free_notification_data (NotificationData *data)
-{
- g_object_unref (data->chat);
- g_slice_free (NotificationData, data);
-}
-
static void
chat_window_notification_closed_cb (NotifyNotification *notify,
- NotificationData *cb_data)
+ EmpathyChatWindow *self)
{
- EmpathyNotificationClosedReason reason = 0;
- EmpathyChatWindowPriv *priv = GET_PRIV (cb_data->window);
-
-#ifdef notify_notification_get_closed_reason
- reason = notify_notification_get_closed_reason (notify);
-#endif
- if (reason == EMPATHY_NOTIFICATION_CLOSED_DISMISSED) {
- empathy_chat_window_present_chat (cb_data->chat);
- }
+ EmpathyChatWindowPriv *priv = GET_PRIV (self);
g_object_unref (notify);
- priv->notification = NULL;
- free_notification_data (cb_data);
- priv->notification_data = NULL;
+ if (priv->notification == notify) {
+ priv->notification = NULL;
+ }
}
static void
const char *body;
GdkPixbuf *pixbuf;
EmpathyChatWindowPriv *priv = GET_PRIV (window);
- gboolean res;
+ gboolean res, has_x_canonical_append;
+ NotifyNotification *notification = priv->notification;
if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
return;
} else {
- empathy_conf_get_bool (empathy_conf_get (),
- EMPATHY_PREFS_NOTIFICATIONS_FOCUS, &res);
+ res = g_settings_get_boolean (priv->gsettings_notif,
+ EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
+
if (!res) {
return;
}
}
sender = empathy_message_get_sender (message);
- header = empathy_contact_get_name (sender);
+ header = empathy_contact_get_alias (sender);
body = empathy_message_get_body (message);
escaped = g_markup_escape_text (body, -1);
-
- if (priv->notification != NULL) {
- notify_notification_update (priv->notification,
+ has_x_canonical_append = empathy_notify_manager_has_capability (
+ priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
+
+ if (notification != NULL && !has_x_canonical_append) {
+ /* if the notification server supports x-canonical-append, it is
+ better to not use notify_notification_update to avoid
+ overwriting the current notification message */
+ notify_notification_update (notification,
header, escaped, NULL);
} else {
- NotificationData *cb_data = cb_data = g_slice_new0 (NotificationData);
+ /* if the notification server supports x-canonical-append,
+ the hint will be added, so that the message from the
+ just created notification will be automatically appended
+ to an existing notification with the same title.
+ In this way the previous message will not be lost: the new
+ message will appear below it, in the same notification */
+ notification = notify_notification_new (header, escaped, NULL);
+
+ if (priv->notification == NULL) {
+ priv->notification = notification;
+ }
- cb_data->chat = g_object_ref (chat);
- cb_data->window = window;
+ notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
- priv->notification_data = cb_data;
- priv->notification = notify_notification_new (header, escaped, NULL, NULL);
- notify_notification_set_timeout (priv->notification, NOTIFY_EXPIRES_DEFAULT);
+ tp_g_signal_connect_object (notification, "closed",
+ G_CALLBACK (chat_window_notification_closed_cb), window, 0);
- g_signal_connect (priv->notification, "closed",
- G_CALLBACK (chat_window_notification_closed_cb), cb_data);
+ if (has_x_canonical_append) {
+ notify_notification_set_hint_string (notification,
+ EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "");
+ }
}
pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
sender, EMPATHY_IMAGE_NEW_MESSAGE);
if (pixbuf != NULL) {
- notify_notification_set_icon_from_pixbuf (priv->notification, pixbuf);
+ notify_notification_set_icon_from_pixbuf (notification, pixbuf);
g_object_unref (pixbuf);
}
- notify_notification_show (priv->notification, NULL);
+ notify_notification_show (notification, NULL);
g_free (escaped);
}
g_free (markup);
}
+static gboolean
+empathy_chat_window_has_focus (EmpathyChatWindow *window)
+{
+ EmpathyChatWindowPriv *priv;
+ gboolean has_focus;
+
+ g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
+
+ priv = GET_PRIV (window);
+
+ g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
+
+ return has_focus;
+}
+
static void
chat_window_new_message_cb (EmpathyChat *chat,
EmpathyMessage *message,
+ gboolean pending,
EmpathyChatWindow *window)
{
EmpathyChatWindowPriv *priv;
sender = empathy_message_get_sender (message);
if (empathy_contact_is_user (sender)) {
- empathy_sound_play (GTK_WIDGET (priv->dialog),
+ empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
EMPATHY_SOUND_MESSAGE_OUTGOING);
}
if (has_focus && priv->current_chat == chat) {
/* window and tab are focused so consider the message to be read */
+
+ /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
empathy_chat_messages_read (chat);
return;
}
/* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
* If empathy_chat_get_remote_contact () returns NULL, that means it's
* an unamed MUC (msn-like).
- * In case of a MUC, we set urgency only if the message contains our
- * alias. */
+ * In case of a MUC, we set urgency if either:
+ * a) the chatroom's always_urgent property is TRUE
+ * b) the message contains our alias
+ */
if (empathy_chat_is_room (chat) ||
empathy_chat_get_remote_contact (chat) == NULL) {
- needs_urgency = empathy_message_should_highlight (message);
+ TpAccount *account;
+ const gchar *room;
+ EmpathyChatroom *chatroom;
+
+ account = empathy_chat_get_account (chat);
+ room = empathy_chat_get_id (chat);
+
+ chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
+ account, room);
+
+ if (empathy_chatroom_is_always_urgent (chatroom)) {
+ needs_urgency = TRUE;
+ } else {
+ needs_urgency = empathy_message_should_highlight (message);
+ }
} else {
needs_urgency = TRUE;
}
chat_window_set_highlight_room_tab_label (chat);
}
- empathy_sound_play (GTK_WIDGET (priv->dialog),
+ empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
EMPATHY_SOUND_MESSAGE_INCOMING);
- chat_window_show_or_update_notification (window, message, chat);
+
+ /* Pending messages have already been displayed in the approver, so we don't
+ * display a notification for those. */
+ if (!pending)
+ chat_window_show_or_update_notification (window, message, chat);
}
- /* update the number of unread messages */
+ /* update the number of unread messages and the window icon */
chat_window_title_update (priv);
+ chat_window_icon_update (priv);
}
static GtkNotebook *
-chat_window_detach_hook (GtkNotebook *source,
+notebook_create_window_cb (GtkNotebook *source,
GtkWidget *page,
gint x,
gint y,
empathy_chat_window_move_chat (window, new_window, chat);
- gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
gtk_widget_show (priv->dialog);
+ gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
return NULL;
}
static void
chat_window_page_switched_cb (GtkNotebook *notebook,
- GtkNotebookPage *page,
+ gpointer ignore, /* see note below */
gint page_num,
EmpathyChatWindow *window)
{
priv = GET_PRIV (window);
+ /* N.B. in GTK+ 3 child is passed as the first argument to the signal,
+ * but we can't use that while trying to retain GTK+ 2.x compatibility.
+ */
child = gtk_notebook_get_nth_page (notebook, page_num);
chat = EMPATHY_CHAT (child);
{
EmpathyChatWindowPriv *priv;
- DEBUG ("Focus in event, updating title");
-
priv = GET_PRIV (window);
priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
}
static gboolean
-chat_window_drag_motion (GtkWidget *widget,
+chat_window_drag_drop (GtkWidget *widget,
GdkDragContext *context,
int x,
int y,
{
GdkAtom target;
EmpathyChatWindowPriv *priv;
- GdkAtom dest_target;
priv = GET_PRIV (window);
- target = gtk_drag_dest_find_target (widget, context, NULL);
+ target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
+ if (target == GDK_NONE)
+ target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
+
+ if (target != GDK_NONE) {
+ gtk_drag_get_data (widget, context, target, time_);
+ return TRUE;
+ }
- dest_target = gdk_atom_intern_static_string ("text/uri-list");
- if (target == dest_target) {
+ return FALSE;
+}
+
+static gboolean
+chat_window_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ guint time_,
+ EmpathyChatWindow *window)
+{
+ GdkAtom target;
+ EmpathyChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
+ if (target != GDK_NONE) {
/* This is a file drag. Ensure the contact is online and set the
drag type to COPY. Note that it's possible that the tab will
be switched by GTK+ after a timeout from drag_motion without
return TRUE;
}
- dest_target = gdk_atom_intern_static_string ("text/contact-id");
- if (target == dest_target) {
+ target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
+ if (target != GDK_NONE) {
/* This is a drag of a contact from a contact list. Set to COPY.
FIXME: If this drag is to a MUC window, it invites the user.
Otherwise, it opens a chat. Should we use a different drag
return TRUE;
}
- /* Otherwise, it must be a notebook tab drag. Set to MOVE. */
- gdk_drag_status (context, GDK_ACTION_MOVE, time_);
- return TRUE;
+ return FALSE;
}
static void
}
if (!chat) {
- TpConnection *connection;
-
- connection = tp_account_get_connection (account);
-
- if (connection) {
- empathy_dispatcher_chat_with_contact_id (
- connection, contact_id, NULL, NULL);
- }
+ empathy_dispatcher_chat_with_contact_id (
+ account, contact_id, gtk_get_current_event_time ());
g_strfreev (strv);
return;
}
/* Added to take care of any outstanding chat events */
- empathy_chat_window_present_chat (chat);
+ empathy_chat_window_present_chat (chat,
+ TP_USER_ACTION_TIME_NOT_USER_ACTION);
/* We should return TRUE to remove the data when doing
* GDK_ACTION_MOVE, but we don't here otherwise it has
EmpathyChatWindowPriv *priv;
priv = GET_PRIV (window);
-
- if (old_window == window) {
- DEBUG ("DND tab (within same window)");
- priv->dnd_same_window = TRUE;
- gtk_drag_finish (context, TRUE, FALSE, time_);
- return;
- }
-
- priv->dnd_same_window = FALSE;
+ priv->dnd_same_window = (old_window == window);
+ DEBUG ("DND tab (within same window: %s)",
+ priv->dnd_same_window ? "Yes" : "No");
}
-
- /* We should return TRUE to remove the data when doing
- * GDK_ACTION_MOVE, but we don't here otherwise it has
- * weird consequences, and we handle that internally
- * anyway with add_chat () and remove_chat ().
- */
- gtk_drag_finish (context, TRUE, FALSE, time_);
} else {
DEBUG ("DND from unknown source");
gtk_drag_finish (context, FALSE, FALSE, time_);
}
}
+static void
+chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
+ guint num_chats_in_manager,
+ EmpathyChatWindow *window)
+{
+ EmpathyChatWindowPriv *priv = GET_PRIV (window);
+
+ gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
+ num_chats_in_manager > 0);
+}
+
static void
chat_window_finalize (GObject *object)
{
g_object_unref (priv->ui_manager);
g_object_unref (priv->chatroom_manager);
g_object_unref (priv->notify_mgr);
+ g_object_unref (priv->gsettings_chat);
+ g_object_unref (priv->gsettings_notif);
+ g_object_unref (priv->gsettings_ui);
+ g_object_unref (priv->sound_mgr);
if (priv->notification != NULL) {
notify_notification_close (priv->notification, NULL);
- g_object_unref (priv->notification);
priv->notification = NULL;
- if (priv->notification_data != NULL)
- {
- free_notification_data (priv->notification_data);
- priv->notification_data = NULL;
- }
}
if (priv->contact_targets) {
gtk_target_list_unref (priv->file_targets);
}
+ if (priv->chat_manager) {
+ g_signal_handler_disconnect (priv->chat_manager,
+ priv->chat_manager_chats_changed_id);
+ g_object_unref (priv->chat_manager);
+ priv->chat_manager = NULL;
+ }
+
chat_windows = g_list_remove (chat_windows, window);
gtk_widget_destroy (priv->dialog);
" ythickness = 0\n"
"}\n"
"widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
-
- gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
}
static void
"ui_manager", &priv->ui_manager,
"menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
"menu_conv_favorite", &priv->menu_conv_favorite,
+ "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
"menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
"menu_edit_cut", &priv->menu_edit_cut,
"menu_edit_copy", &priv->menu_edit_copy,
"menu_edit_paste", &priv->menu_edit_paste,
+ "menu_edit_find", &priv->menu_edit_find,
"menu_tabs_next", &priv->menu_tabs_next,
"menu_tabs_prev", &priv->menu_tabs_prev,
+ "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
"menu_tabs_left", &priv->menu_tabs_left,
"menu_tabs_right", &priv->menu_tabs_right,
"menu_tabs_detach", &priv->menu_tabs_detach,
"menu_conv", "activate", chat_window_conv_activate_cb,
"menu_conv_clear", "activate", chat_window_clear_activate_cb,
"menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
+ "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
"menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
"menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
"menu_conv_close", "activate", chat_window_close_activate_cb,
"menu_edit_cut", "activate", chat_window_cut_activate_cb,
"menu_edit_copy", "activate", chat_window_copy_activate_cb,
"menu_edit_paste", "activate", chat_window_paste_activate_cb,
+ "menu_edit_find", "activate", chat_window_find_activate_cb,
"menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
"menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
+ "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
"menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
"menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
"menu_tabs_detach", "activate", chat_window_detach_activate_cb,
g_object_ref (priv->ui_manager);
g_object_unref (gui);
+ priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
+ priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
+ priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
+ priv->sound_mgr = empathy_sound_manager_dup_singleton ();
+
priv->notebook = gtk_notebook_new ();
- gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow");
+
+ g_signal_connect (priv->notebook, "create-window",
+ G_CALLBACK (notebook_create_window_cb), window);
+
+ gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
+ "EmpathyChatWindow");
gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
/* Set up drag and drop */
gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
- GTK_DEST_DEFAULT_ALL,
+ GTK_DEST_DEFAULT_HIGHLIGHT,
drag_types_dest,
G_N_ELEMENTS (drag_types_dest),
GDK_ACTION_MOVE | GDK_ACTION_COPY);
"drag-data-received",
G_CALLBACK (chat_window_drag_data_received),
window);
+ g_signal_connect (priv->notebook,
+ "drag-drop",
+ G_CALLBACK (chat_window_drag_drop),
+ window);
chat_windows = g_list_prepend (chat_windows, window);
priv->chats_new_msg = NULL;
priv->chats_composing = NULL;
priv->current_chat = NULL;
+ priv->notification = NULL;
priv->notify_mgr = empathy_notify_manager_dup_singleton ();
+
+ priv->chat_manager = empathy_chat_manager_dup_singleton ();
+ priv->chat_manager_chats_changed_id =
+ g_signal_connect (priv->chat_manager, "closed-chats-changed",
+ G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
+ window);
+
+ chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
+ empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
+ window);
}
-EmpathyChatWindow *
-empathy_chat_window_new (void)
+static GtkWidget *
+empathy_chat_window_get_dialog (EmpathyChatWindow *window)
{
- return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
+ EmpathyChatWindowPriv *priv;
+
+ g_return_val_if_fail (window != NULL, NULL);
+
+ priv = GET_PRIV (window);
+
+ return priv->dialog;
}
-/* Returns the window to open a new tab in if there is only one window
- * visble, otherwise, returns NULL indicating that a new window should
- * be added.
+/* Returns the window to open a new tab in if there is a suitable window,
+ * otherwise, returns NULL indicating that a new window should be added.
*/
-EmpathyChatWindow *
+static EmpathyChatWindow *
empathy_chat_window_get_default (gboolean room)
{
+ GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
GList *l;
gboolean separate_windows = TRUE;
- empathy_conf_get_bool (empathy_conf_get (),
- EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
- &separate_windows);
+ separate_windows = g_settings_get_boolean (gsettings,
+ EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
+
+ g_object_unref (gsettings);
if (separate_windows) {
/* Always create a new window */
EmpathyChatWindowPriv *priv;
EmpathyChatWindow *chat_window;
GtkWidget *dialog;
+ guint nb_rooms, nb_private;
chat_window = l->data;
priv = GET_PRIV (chat_window);
dialog = empathy_chat_window_get_dialog (chat_window);
- if (empathy_window_get_is_visible (GTK_WINDOW (dialog))) {
- guint nb_rooms, nb_private;
- empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
- /* Skip the window if there aren't any rooms in it */
- if (room && nb_rooms == 0)
- continue;
+ empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
- /* Skip the window if there aren't any 1-1 chats in it */
- if (!room && nb_private == 0)
- continue;
+ /* Skip the window if there aren't any rooms in it */
+ if (room && nb_rooms == 0)
+ continue;
- /* Found a visible window on this desktop */
- return chat_window;
- }
+ /* Skip the window if there aren't any 1-1 chats in it */
+ if (!room && nb_private == 0)
+ continue;
+
+ /* Found a window on this desktop, make it visible if necessary */
+ if (!empathy_window_get_is_visible (GTK_WINDOW (dialog)))
+ empathy_window_present (GTK_WINDOW (dialog));
+ return chat_window;
}
return NULL;
}
-GtkWidget *
-empathy_chat_window_get_dialog (EmpathyChatWindow *window)
-{
- EmpathyChatWindowPriv *priv;
-
- g_return_val_if_fail (window != NULL, NULL);
-
- priv = GET_PRIV (window);
-
- return priv->dialog;
-}
-
-void
+static void
empathy_chat_window_add_chat (EmpathyChatWindow *window,
EmpathyChat *chat)
{
GtkWidget *label;
GtkWidget *popup_label;
GtkWidget *child;
+ GValue value = { 0, };
g_return_if_fail (window != NULL);
g_return_if_fail (EMPATHY_IS_CHAT (chat));
const gchar *name = "chat-window";
gboolean separate_windows;
- empathy_conf_get_bool (empathy_conf_get (),
- EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
- &separate_windows);
+ separate_windows = g_settings_get_boolean (priv->gsettings_ui,
+ EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
+
+ if (empathy_chat_is_room (chat))
+ name = "room-window";
if (separate_windows) {
+ gint x, y;
+
+ /* Save current position of the window */
+ gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
+
+ /* First bind to the 'generic' name. So new window for which we didn't
+ * save a geometry yet will have the geometry of the last saved
+ * window (bgo #601191). */
+ empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
+
+ /* Restore previous position of the window so the newly created window
+ * won't be in the same position as the latest saved window and so
+ * completely hide it. */
+ gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
+
+ /* Then bind it to the name of the contact/room so we'll save the
+ * geometry specific to this window */
name = empathy_chat_get_id (chat);
}
- else if (empathy_chat_is_room (chat)) {
- name = "room-window";
- }
empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
}
gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
- gtk_notebook_set_tab_label_packing (GTK_NOTEBOOK (priv->notebook), child,
- TRUE, TRUE, GTK_PACK_START);
+ g_value_init (&value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&value, TRUE);
+ gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
+ child, "tab-expand" , &value);
+ gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
+ child, "tab-fill" , &value);
+ g_value_unset (&value);
DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
}
-void
+static void
empathy_chat_window_remove_chat (EmpathyChatWindow *window,
EmpathyChat *chat)
{
EmpathyChatWindowPriv *priv;
gint position;
EmpathyContact *remote_contact;
+ EmpathyChatManager *chat_manager;
g_return_if_fail (window != NULL);
g_return_if_fail (EMPATHY_IS_CHAT (chat));
chat);
}
+ chat_manager = empathy_chat_manager_dup_singleton ();
+ empathy_chat_manager_closed_chat (chat_manager, chat);
+ g_object_unref (chat_manager);
+
position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
GTK_WIDGET (chat));
gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
g_object_unref (chat);
}
-void
+static void
empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
EmpathyChatWindow *new_window,
EmpathyChat *chat)
g_object_unref (chat);
}
-void
+static void
empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
EmpathyChat *chat)
{
page_num);
}
-gboolean
-empathy_chat_window_has_focus (EmpathyChatWindow *window)
-{
- EmpathyChatWindowPriv *priv;
- gboolean has_focus;
-
- g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
-
- priv = GET_PRIV (window);
-
- g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
-
- return has_focus;
-}
-
EmpathyChat *
empathy_chat_window_find_chat (TpAccount *account,
const gchar *id)
}
void
-empathy_chat_window_present_chat (EmpathyChat *chat)
+empathy_chat_window_present_chat (EmpathyChat *chat,
+ gint64 timestamp)
{
EmpathyChatWindow *window;
EmpathyChatWindowPriv *priv;
+ guint32 x_timestamp;
g_return_if_fail (EMPATHY_IS_CHAT (chat));
window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
if (!window) {
window = empathy_chat_window_new ();
+ gtk_widget_show_all (GET_PRIV (window)->dialog);
}
empathy_chat_window_add_chat (window, chat);
}
+ /* Don't force the window to show itself when it wasn't
+ * an action by the user
+ */
+ if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
+ return;
+
priv = GET_PRIV (window);
+
+ if (x_timestamp != GDK_CURRENT_TIME) {
+ /* Don't present or switch tab if the action was earlier than the
+ * last actions X time, accounting for overflow and the first ever
+ * presentation */
+
+ if (priv->x_user_action_time != 0
+ && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
+ return;
+
+ priv->x_user_action_time = x_timestamp;
+ }
+
empathy_chat_window_switch_to_chat (window, chat);
- empathy_window_present (GTK_WINDOW (priv->dialog), TRUE);
+ empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
+ x_timestamp);
- gtk_widget_grab_focus (chat->input_text_view);
+ gtk_widget_grab_focus (chat->input_text_view);
}
-void
+static void
empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
guint *nb_rooms,
guint *nb_private)