X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=libempathy-gtk%2Fempathy-chat.c;h=d273a3792536b161095434f2e5c31d7f4a052478;hp=25203b46eb5e40bf83be672294b4dcf5de11cdc5;hb=9b3306a89a8419217e9c2350d7c32c1a83704eca;hpb=f5d7d6d3a1d7a09e9e3c0cbf013a8456d33537dd diff --git a/libempathy-gtk/empathy-chat.c b/libempathy-gtk/empathy-chat.c index 25203b46..d273a379 100644 --- a/libempathy-gtk/empathy-chat.c +++ b/libempathy-gtk/empathy-chat.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -47,13 +48,13 @@ #include "empathy-chat.h" #include "empathy-spell.h" -#include "empathy-contact-list-store.h" -#include "empathy-contact-list-view.h" -#include "empathy-contact-menu.h" -#include "empathy-gtk-marshal.h" +#include "empathy-contact-dialogs.h" +#include "empathy-individual-store-channel.h" +#include "empathy-individual-view.h" #include "empathy-input-text-view.h" #include "empathy-search-bar.h" #include "empathy-theme-manager.h" +#include "empathy-theme-adium.h" #include "empathy-smiley-manager.h" #include "empathy-ui-utils.h" #include "empathy-string-parser.h" @@ -72,6 +73,7 @@ struct _EmpathyChatPriv { gchar *id; gchar *name; gchar *subject; + EmpathyContact *self_contact; EmpathyContact *remote_contact; gboolean show_contacts; @@ -104,6 +106,8 @@ struct _EmpathyChatPriv { guint update_misspelled_words_id; /* Source func ID for save_paned_pos_timeout () */ guint save_paned_pos_id; + /* Source func ID for chat_contacts_visible_timeout_cb () */ + guint contacts_visible_id; GtkWidget *widget; GtkWidget *hpaned; @@ -119,6 +123,7 @@ struct _EmpathyChatPriv { GtkWidget *search_bar; guint unread_messages; + guint unread_messages_when_offline; /* TRUE if the pending messages can be displayed. This is to avoid to show * pending messages *before* messages from logs. (#603980) */ gboolean can_show_pending; @@ -146,6 +151,19 @@ struct _EmpathyChatPriv { * messages in tab will be properly shown */ gboolean retrieving_backlogs; gboolean sms_channel; + + /* we need to know whether populate-popup happened in response to + * the keyboard or the mouse. We can't ask GTK for the most recent + * event, because it will be a notify event. Instead we track it here */ + GdkEventType most_recent_event_type; + + /* A regex matching our own current nickname in the room, or %NULL if + * !empathy_chat_is_room (). */ + GRegex *highlight_regex; + + /* TRUE if empathy_chat_is_room () and there are unread highlighted messages. + * Cleared by empathy_chat_messages_read (). */ + gboolean highlighted; }; typedef struct { @@ -173,11 +191,12 @@ enum { PROP_SHOW_CONTACTS, PROP_SMS_CHANNEL, PROP_N_MESSAGES_SENDING, + PROP_NB_UNREAD_MESSAGES, }; static guint signals[LAST_SIGNAL] = { 0 }; -G_DEFINE_TYPE (EmpathyChat, empathy_chat, GTK_TYPE_BIN); +G_DEFINE_TYPE (EmpathyChat, empathy_chat, GTK_TYPE_BOX); static gboolean update_misspelled_words (gpointer data); @@ -219,6 +238,10 @@ chat_get_property (GObject *object, g_value_set_uint (value, empathy_chat_get_n_messages_sending (chat)); break; + case PROP_NB_UNREAD_MESSAGES: + g_value_set_uint (value, + empathy_chat_get_nb_unread_messages (chat)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; @@ -262,11 +285,13 @@ account_reconnected (EmpathyChat *chat, if (priv->sms_channel) empathy_sms_contact_id ( account, priv->id, - TP_USER_ACTION_TIME_NOT_USER_ACTION); + TP_USER_ACTION_TIME_NOT_USER_ACTION, + NULL, NULL); else empathy_chat_with_contact_id ( account, priv->id, - TP_USER_ACTION_TIME_NOT_USER_ACTION); + TP_USER_ACTION_TIME_NOT_USER_ACTION, + NULL, NULL); break; case TP_HANDLE_TYPE_ROOM: empathy_join_muc (account, priv->id, @@ -320,16 +345,50 @@ chat_composing_remove_timeout (EmpathyChat *chat) } } +static void +set_chate_state_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GError *error = NULL; + + if (!tp_text_channel_set_chat_state_finish (TP_TEXT_CHANNEL (source), result, + &error)) { + DEBUG ("Failed to set chat state: %s", error->message); + g_error_free (error); + } +} + +static void +set_chat_state (EmpathyChat *self, + TpChannelChatState state) +{ + EmpathyChatPriv *priv = GET_PRIV (self); + + if (!tp_proxy_has_interface_by_id (priv->tp_chat, + TP_IFACE_QUARK_CHANNEL_INTERFACE_CHAT_STATE)) + return; + + tp_text_channel_set_chat_state_async (TP_TEXT_CHANNEL (priv->tp_chat), state, + set_chate_state_cb, self); +} + static gboolean chat_composing_stop_timeout_cb (EmpathyChat *chat) { EmpathyChatPriv *priv; + gboolean send_chat_states; priv = GET_PRIV (chat); priv->composing_stop_timeout_id = 0; - empathy_tp_chat_set_state (priv->tp_chat, - TP_CHANNEL_CHAT_STATE_PAUSED); + send_chat_states = g_settings_get_boolean (priv->gsettings_chat, + EMPATHY_PREFS_CHAT_SEND_CHAT_STATES); + if (!send_chat_states) { + set_chat_state (chat, TP_CHANNEL_CHAT_STATE_ACTIVE); + } else { + set_chat_state (chat, TP_CHANNEL_CHAT_STATE_PAUSED); + } return FALSE; } @@ -338,15 +397,21 @@ static void chat_composing_start (EmpathyChat *chat) { EmpathyChatPriv *priv; + gboolean send_chat_states; priv = GET_PRIV (chat); + send_chat_states = g_settings_get_boolean (priv->gsettings_chat, + EMPATHY_PREFS_CHAT_SEND_CHAT_STATES); + if (!send_chat_states) { + return; + } + if (priv->composing_stop_timeout_id) { /* Just restart the timeout */ chat_composing_remove_timeout (chat); } else { - empathy_tp_chat_set_state (priv->tp_chat, - TP_CHANNEL_CHAT_STATE_COMPOSING); + set_chat_state (chat, TP_CHANNEL_CHAT_STATE_COMPOSING); } priv->composing_stop_timeout_id = g_timeout_add_seconds ( @@ -358,13 +423,8 @@ chat_composing_start (EmpathyChat *chat) static void chat_composing_stop (EmpathyChat *chat) { - EmpathyChatPriv *priv; - - priv = GET_PRIV (chat); - chat_composing_remove_timeout (chat); - empathy_tp_chat_set_state (priv->tp_chat, - TP_CHANNEL_CHAT_STATE_ACTIVE); + set_chat_state (chat, TP_CHANNEL_CHAT_STATE_ACTIVE); } static gint @@ -694,7 +754,7 @@ nick_command_supported (EmpathyChat *chat) EmpathyChatPriv * priv = GET_PRIV (chat); TpConnection *connection; - connection = empathy_tp_chat_get_connection (priv->tp_chat); + connection = tp_channel_borrow_connection (TP_CHANNEL (priv->tp_chat)); return tp_proxy_has_interface_by_id (connection, EMP_IFACE_QUARK_CONNECTION_INTERFACE_RENAMING); } @@ -703,10 +763,8 @@ static gboolean part_command_supported (EmpathyChat *chat) { EmpathyChatPriv * priv = GET_PRIV (chat); - TpChannel *channel; - channel = empathy_tp_chat_get_channel (priv->tp_chat); - return tp_proxy_has_interface_by_id (channel, + return tp_proxy_has_interface_by_id (priv->tp_chat, TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP); } @@ -722,26 +780,20 @@ chat_command_topic (EmpathyChat *chat, GStrv strv) { EmpathyChatPriv *priv = GET_PRIV (chat); - EmpathyTpChatProperty *property; - GValue value = {0, }; - property = empathy_tp_chat_get_property (priv->tp_chat, "subject"); - if (property == NULL) { + if (!empathy_tp_chat_supports_subject (priv->tp_chat)) { empathy_chat_view_append_event (chat->view, _("Topic not supported on this conversation")); return; } - if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) { + if (!empathy_tp_chat_can_set_subject (priv->tp_chat)) { empathy_chat_view_append_event (chat->view, _("You are not allowed to change the topic")); return; } - g_value_init (&value, G_TYPE_STRING); - g_value_set_string (&value, strv[1]); - empathy_tp_chat_set_property (priv->tp_chat, "subject", &value); - g_value_unset (&value); + empathy_tp_chat_set_subject (priv->tp_chat, strv[1]); } void @@ -860,24 +912,19 @@ chat_command_me (EmpathyChat *chat, { EmpathyChatPriv *priv = GET_PRIV (chat); TpMessage *message; - TpChannel *channel; + TpTextChannel *channel; - channel = empathy_tp_chat_get_channel (priv->tp_chat); + channel = (TpTextChannel *) (priv->tp_chat); - /* Strictly speaking we don't depend yet on Messages so best to check that - * the channel is actually a TpTextChannel before casting it. */ - if (TP_IS_TEXT_CHANNEL (channel) && - !tp_text_channel_supports_message_type (TP_TEXT_CHANNEL (channel), + if (!tp_text_channel_supports_message_type (channel, TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION)) { /* Action message are not supported, 'simulate' the action */ - EmpathyContact *self_contact; gchar *tmp; - self_contact = empathy_tp_chat_get_self_contact (priv->tp_chat); /* The TpChat can't be ready if it doesn't have the self contact */ - g_assert (self_contact != NULL); + g_assert (priv->self_contact != NULL); - tmp = g_strdup_printf ("%s %s", empathy_contact_get_alias (self_contact), + tmp = g_strdup_printf ("%s %s", empathy_contact_get_alias (priv->self_contact), strv[1]); message = tp_client_message_new_text (TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, tmp); @@ -905,6 +952,122 @@ chat_command_say (EmpathyChat *chat, g_object_unref (message); } +static void +whois_got_contact_cb (TpConnection *connection, + guint n_contacts, + TpContact * const *contacts, + const gchar * const *requested_ids, + GHashTable *failed_id_errors, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + EmpathyChat *chat = EMPATHY_CHAT (weak_object); + + g_return_if_fail (n_contacts <= 1); + + if (n_contacts == 0) { + GHashTableIter iter; + gpointer key = NULL, value = NULL; + gchar *id; + GError *id_error; + + /* tp-glib guarantees that the contacts you requested will be + * in failed_id_errors regardless of whether the individual + * contact was invalid or the whole operation failed. + */ + g_hash_table_iter_init (&iter, failed_id_errors); + g_hash_table_iter_next (&iter, &key, &value); + id = key; + id_error = value; + + DEBUG ("Error getting TpContact for '%s': %s %u %s", + id, g_quark_to_string (id_error->domain), + id_error->code, id_error->message); + + if (error == NULL) { + /* The specific ID failed. */ + gchar *event = g_strdup_printf ( + _("“%s” is not a valid contact ID"), id); + empathy_chat_view_append_event (chat->view, event); + g_free (event); + } + /* Otherwise we're disconnected or something; so the window + * will already say ‘Disconnected’, so let's not show anything. + */ + } else { + EmpathyContact *empathy_contact; + GtkWidget *window; + + g_return_if_fail (contacts[0] != NULL); + empathy_contact = empathy_contact_dup_from_tp_contact ( + contacts[0]); + + window = gtk_widget_get_toplevel (GTK_WIDGET (chat)); + /* If we're alive and this command is running, we'd better be + * in a window. */ + g_return_if_fail (window != NULL); + g_return_if_fail (gtk_widget_is_toplevel (window)); + empathy_contact_information_dialog_show (empathy_contact, + GTK_WINDOW (window)); + g_object_unref (empathy_contact); + } +} + +static void +chat_command_whois (EmpathyChat *chat, + GStrv strv) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + TpConnection *conn; + + conn = tp_channel_borrow_connection ((TpChannel *) priv->tp_chat); + tp_connection_get_contacts_by_id (conn, + /* Element 0 of 'strv' is "whois"; element 1 is the contact ID + * entered by the user (including spaces, if any). */ + 1, (const gchar * const *) strv + 1, + 0, NULL, + whois_got_contact_cb, NULL, NULL, G_OBJECT (chat)); +} + +static void +chat_command_whale (EmpathyChat *chat, + GStrv strv) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + TpMessage *message; + + message = tp_client_message_new_text (TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, + "\n\n\n" + "•_______________•"); + empathy_tp_chat_send (priv->tp_chat, message); + g_object_unref (message); +} + +static void +chat_command_babywhale (EmpathyChat *chat, + GStrv strv) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + TpMessage *message; + + message = tp_client_message_new_text (TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, + "\n" + "•_____•"); + empathy_tp_chat_send (priv->tp_chat, message); + g_object_unref (message); +} + +static void +chat_command_inspector (EmpathyChat *chat, + GStrv strv) +{ + if (EMPATHY_IS_THEME_ADIUM (chat->view)) { + empathy_theme_adium_show_inspector ( + EMPATHY_THEME_ADIUM (chat->view)); + } +} + static void chat_command_help (EmpathyChat *chat, GStrv strv); typedef void (*ChatCommandFunc) (EmpathyChat *chat, GStrv strv); @@ -953,9 +1116,17 @@ static ChatCommandItem commands[] = { "This is used to send a message starting with a '/'. For example: " "\"/say /join is used to join a new chat room\"")}, + {"whois", 2, 2, chat_command_whois, NULL, + N_("/whois : display information about a contact")}, + {"help", 1, 2, chat_command_help, NULL, N_("/help []: show all supported commands. " "If is defined, show its usage.")}, + + {"inspector", 1, 1, chat_command_inspector, NULL, NULL}, + + {"whale", 1, 1, chat_command_whale, NULL, NULL}, + {"babywhale", 1, 1, chat_command_babywhale, NULL, NULL}, }; static void @@ -964,6 +1135,10 @@ chat_command_show_help (EmpathyChat *chat, { gchar *str; + if (item->help == NULL) { + return; + } + str = g_strdup_printf (_("Usage: %s"), _(item->help)); empathy_chat_view_append_event (chat->view, str); g_free (str); @@ -984,8 +1159,11 @@ chat_command_help (EmpathyChat *chat, continue; } } - empathy_chat_view_append_event (chat->view, - _(commands[i].help)); + if (commands[i].help == NULL) { + continue; + } + empathy_chat_view_append_event (chat->view, + _(commands[i].help)); } return; } @@ -997,6 +1175,9 @@ chat_command_help (EmpathyChat *chat, break; } } + if (commands[i].help == NULL) { + break; + } chat_command_show_help (chat, &commands[i]); return; } @@ -1223,6 +1404,86 @@ chat_state_changed_cb (EmpathyTpChat *tp_chat, } } +static GRegex * +get_highlight_regex_for (const gchar *name) +{ + GRegex *regex; + gchar *name_esc, *pattern; + GError *error = NULL; + + name_esc = g_regex_escape_string (name, -1); + pattern = g_strdup_printf ("\\b%s\\b", name_esc); + regex = g_regex_new (pattern, G_REGEX_CASELESS | G_REGEX_OPTIMIZE, 0, + &error); + + if (regex == NULL) { + DEBUG ("couldn't compile regex /%s/: %s", pattern, + error->message); + + g_error_free (error); + } + + g_free (pattern); + g_free (name_esc); + + return regex; +} + +/* Called when priv->self_contact changes, or priv->self_contact:alias changes. + * Only connected if empathy_chat_is_room() is TRUE, for obvious-ish reasons. + */ +static void +chat_self_contact_alias_changed_cb (EmpathyChat *chat) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + + tp_clear_pointer (&priv->highlight_regex, g_regex_unref); + + if (priv->self_contact != NULL) { + const gchar *alias = empathy_contact_get_alias (priv->self_contact); + + g_return_if_fail (alias != NULL); + priv->highlight_regex = get_highlight_regex_for (alias); + } +} + +static gboolean +chat_should_highlight (EmpathyChat *chat, + EmpathyMessage *message) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + const gchar *msg; + TpChannelTextMessageFlags flags; + + g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), FALSE); + + if (!empathy_chat_is_room (chat)) { + return FALSE; + } + + if (!empathy_message_is_incoming (message)) { + return FALSE; + } + + msg = empathy_message_get_body (message); + if (!msg) { + return FALSE; + } + + flags = empathy_message_get_flags (message); + if (flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_SCROLLBACK) { + /* FIXME: Ideally we shouldn't highlight scrollback messages only if they + * have already been received by the user before (and so are in the logs) */ + return FALSE; + } + + if (priv->highlight_regex == NULL) { + return FALSE; + } + + return g_regex_match (priv->highlight_regex, msg, 0, NULL); +} + static void chat_message_received (EmpathyChat *chat, EmpathyMessage *message, @@ -1233,19 +1494,40 @@ chat_message_received (EmpathyChat *chat, sender = empathy_message_get_sender (message); - DEBUG ("Appending new message from %s (%d)", - empathy_contact_get_alias (sender), - empathy_contact_get_handle (sender)); + if (empathy_message_is_edit (message)) { + DEBUG ("Editing message '%s' to '%s'", + empathy_message_get_supersedes (message), + empathy_message_get_body (message)); + + empathy_chat_view_edit_message (chat->view, message); + } else { + gboolean should_highlight = chat_should_highlight (chat, message); + + if (should_highlight) { + priv->highlighted = TRUE; + } + + DEBUG ("Appending new message '%s' from %s (%d)", + empathy_message_get_token (message), + empathy_contact_get_alias (sender), + empathy_contact_get_handle (sender)); + + empathy_chat_view_append_message (chat->view, message, should_highlight); + + if (empathy_message_is_incoming (message)) { + priv->unread_messages++; + g_object_notify (G_OBJECT (chat), "nb-unread-messages"); + } - empathy_chat_view_append_message (chat->view, message); + g_signal_emit (chat, signals[NEW_MESSAGE], 0, message, pending, + should_highlight); + } - /* We received a message so the contact is no longer composing */ + /* We received a message so the contact is no longer + * composing */ chat_state_changed_cb (priv->tp_chat, sender, TP_CHANNEL_CHAT_STATE_ACTIVE, chat); - - priv->unread_messages++; - g_signal_emit (chat, signals[NEW_MESSAGE], 0, message, pending); } static void @@ -1256,6 +1538,67 @@ chat_message_received_cb (EmpathyTpChat *tp_chat, chat_message_received (chat, message, FALSE); } +static void +chat_message_acknowledged_cb (EmpathyTpChat *tp_chat, + EmpathyMessage *message, + EmpathyChat *chat) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + + empathy_chat_view_message_acknowledged (chat->view, + message); + + if (!empathy_message_is_edit (message)) { + priv->unread_messages--; + g_object_notify (G_OBJECT (chat), "nb-unread-messages"); + } +} + +static void +append_balance_error (EmpathyChat *chat, + const gchar *message_body) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + TpConnection *conn = tp_channel_borrow_connection (TP_CHANNEL (priv->tp_chat)); + const gchar *uri = tp_connection_get_balance_uri (conn); + const gchar *error = _("insufficient balance to send message"); + gchar *str, *str_markup = NULL; + + if (message_body != NULL) { + str = g_strdup_printf (_("Error sending message '%s': %s"), message_body, error); + } else { + str = g_strdup_printf (_("Error sending message: %s"), error); + } + + if (!tp_str_empty (uri)) { + /* translators: error used when user doesn't have enough credit on his + * account to send the message. */ + gchar *markup_error = g_strdup_printf (_("insufficient balance to send message." + " Top up."), uri); + + if (message_body != NULL) { + gchar *escaped_body = g_markup_escape_text (message_body, -1); + + str_markup = g_strdup_printf (_("Error sending message '%s': %s"), + escaped_body, markup_error); + + g_free (escaped_body); + } else { + str_markup = g_strdup_printf (_("Error sending message: %s"), markup_error); + } + + g_free (markup_error); + } + + if (str_markup != NULL) + empathy_chat_view_append_event_markup (chat->view, str_markup, str); + else + empathy_chat_view_append_event (chat->view, str); + + g_free (str); + g_free (str_markup); +} + static void chat_send_error_cb (EmpathyTpChat *tp_chat, const gchar *message_body, @@ -1267,9 +1610,8 @@ chat_send_error_cb (EmpathyTpChat *tp_chat, gchar *str; if (!tp_strdiff (dbus_error, TP_ERROR_STR_INSUFFICIENT_BALANCE)) { - /* translators: error used when user doesn't have enough credit on his - * account to send the message. */ - error = _("insufficient balance to send message"); + append_balance_error (chat, message_body); + return; } else if (!tp_strdiff (dbus_error, TP_ERROR_STR_NOT_CAPABLE)) { error = _("not capable"); } @@ -1345,16 +1687,12 @@ chat_topic_expander_activate_cb (GtkExpander *expander, } static void -chat_property_changed_cb (EmpathyTpChat *tp_chat, - const gchar *name, - GValue *value, - EmpathyChat *chat) +chat_subject_changed_cb (EmpathyChat *chat) { EmpathyChatPriv *priv = GET_PRIV (chat); - if (!tp_strdiff (name, "subject")) { g_free (priv->subject); - priv->subject = g_value_dup_string (value); + priv->subject = g_strdup (empathy_tp_chat_get_subject (priv->tp_chat)); g_object_notify (G_OBJECT (chat), "subject"); if (EMP_STR_EMPTY (priv->subject)) { @@ -1374,22 +1712,37 @@ chat_property_changed_cb (EmpathyTpChat *tp_chat, gtk_widget_show (priv->hbox_topic); } if (priv->block_events_timeout_id == 0) { - gchar *str; + gchar *str = NULL; if (!EMP_STR_EMPTY (priv->subject)) { - str = g_strdup_printf (_("Topic set to: %s"), priv->subject); - } else { + const gchar *actor = empathy_tp_chat_get_subject_actor (priv->tp_chat); + + if (tp_str_empty (actor)) { + str = g_strdup_printf (_("Topic set to: %s"), priv->subject); + } else { + str = g_strdup_printf (_("Topic set by %s to: %s"), + actor, priv->subject); + } + } else if (empathy_tp_chat_supports_subject (priv->tp_chat)) { + /* No need to display this 'event' is no topic can be defined anyway */ str = g_strdup (_("No topic defined")); } - empathy_chat_view_append_event (EMPATHY_CHAT (chat)->view, str); - g_free (str); + + if (str != NULL) { + empathy_chat_view_append_event (EMPATHY_CHAT (chat)->view, str); + g_free (str); + } } - } - else if (!tp_strdiff (name, "name")) { +} + +static void +chat_title_changed_cb (EmpathyChat *chat) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + g_free (priv->name); - priv->name = g_value_dup_string (value); + priv->name = g_strdup (empathy_tp_chat_get_title (priv->tp_chat)); g_object_notify (G_OBJECT (chat), "name"); - } } static gboolean @@ -1567,6 +1920,8 @@ chat_input_key_press_event_cb (GtkWidget *widget, priv = GET_PRIV (chat); + priv->most_recent_event_type = event->type; + /* Catch ctrl+up/down so we can traverse messages we sent */ if ((event->state & GDK_CONTROL_MASK) && (event->keyval == GDK_KEY_Up || @@ -2022,11 +2377,24 @@ chat_text_send_cb (GtkMenuItem *menuitem, chat_input_text_view_send (chat); } +static gboolean +chat_input_button_press_event_cb (GtkTextView *view, + GdkEventButton *event, + EmpathyChat *chat) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + + priv->most_recent_event_type = event->type; + + return FALSE; +} + static void chat_input_populate_popup_cb (GtkTextView *view, GtkMenu *menu, EmpathyChat *chat) { + EmpathyChatPriv *priv = GET_PRIV (chat); GtkTextBuffer *buffer; GtkTextTagTable *table; GtkTextTag *tag; @@ -2077,12 +2445,34 @@ chat_input_populate_popup_cb (GtkTextView *view, /* Add the spell check menu item. */ table = gtk_text_buffer_get_tag_table (buffer); tag = gtk_text_tag_table_lookup (table, "misspelled"); - gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y); - gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), - GTK_TEXT_WINDOW_WIDGET, - x, y, - &x, &y); - gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y); + + switch (priv->most_recent_event_type) { + case GDK_BUTTON_PRESS: + /* get the location from the pointer */ + gdk_window_get_device_position (gtk_widget_get_window (GTK_WIDGET (view)), + gdk_device_manager_get_client_pointer (gdk_display_get_device_manager ( + gtk_widget_get_display (GTK_WIDGET (view)))), &x, &y, NULL); + + gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), + GTK_TEXT_WINDOW_WIDGET, + x, y, + &x, &y); + gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), + &iter, x, y); + break; + + default: + g_warn_if_reached (); + /* assume the KEY_PRESS case */ + + case GDK_KEY_PRESS: + /* get the location from the cursor */ + gtk_text_buffer_get_iter_at_mark (buffer, &iter, + gtk_text_buffer_get_insert (buffer)); + break; + + } + start = end = iter; if (gtk_text_iter_backward_to_tag_toggle (&start, tag) && gtk_text_iter_forward_to_tag_toggle (&end, tag)) { @@ -2127,7 +2517,7 @@ chat_input_populate_popup_cb (GtkTextView *view, static gboolean -chat_log_filter (TplEvent *log, +chat_log_filter (TplEvent *event, gpointer user_data) { EmpathyChat *chat = user_data; @@ -2135,11 +2525,11 @@ chat_log_filter (TplEvent *log, EmpathyChatPriv *priv = GET_PRIV (chat); const GList *pending; - g_return_val_if_fail (TPL_IS_EVENT (log), FALSE); + g_return_val_if_fail (TPL_IS_EVENT (event), FALSE); g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE); pending = empathy_tp_chat_get_pending_messages (priv->tp_chat); - message = empathy_message_from_tpl_log_event (log); + message = empathy_message_from_tpl_log_event (event); for (; pending; pending = g_list_next (pending)) { if (empathy_message_equal (message, pending->data)) { @@ -2197,12 +2587,40 @@ got_filtered_messages_cb (GObject *manager, for (l = messages; l; l = g_list_next (l)) { EmpathyMessage *message; + g_assert (TPL_IS_EVENT (l->data)); message = empathy_message_from_tpl_log_event (l->data); g_object_unref (l->data); - empathy_chat_view_append_message (chat->view, message); + if (empathy_message_is_edit (message)) { + /* this is an edited message, create a synthetic event + * using the supersedes token and + * original-message-sent timestamp, that we can then + * replace */ + EmpathyMessage *syn_msg = g_object_new ( + EMPATHY_TYPE_MESSAGE, + "body", "", + "token", empathy_message_get_supersedes (message), + "type", empathy_message_get_tptype (message), + "timestamp", empathy_message_get_original_timestamp (message), + "incoming", empathy_message_is_incoming (message), + "is-backlog", TRUE, + "receiver", empathy_message_get_receiver (message), + "sender", empathy_message_get_sender (message), + NULL); + + empathy_chat_view_append_message (chat->view, syn_msg, + chat_should_highlight (chat, syn_msg)); + empathy_chat_view_edit_message (chat->view, message); + + g_object_unref (syn_msg); + } else { + /* append the latest message */ + empathy_chat_view_append_message (chat->view, message, + chat_should_highlight (chat, message)); + } + g_object_unref (message); } g_list_free (messages); @@ -2395,9 +2813,22 @@ chat_member_renamed_cb (EmpathyTpChat *tp_chat, } static gboolean -chat_reset_size_request (gpointer widget) +chat_contacts_visible_timeout_cb (gpointer chat) { - gtk_widget_set_size_request (widget, -1, -1); + EmpathyChatPriv *priv = GET_PRIV (chat); + + /* Relax the size request */ + gtk_widget_set_size_request (priv->vbox_left, -1, -1); + + /* Set the position of the slider. This must be done here because + * GtkPaned need to know its size allocation and it will be settled only + * after the gtk_window_resize () tough effect. */ + if (priv->contacts_width > 0) { + gtk_paned_set_position (GTK_PANED (priv->hpaned), + priv->contacts_width); + } + + priv->contacts_visible_id = 0; return FALSE; } @@ -2407,7 +2838,6 @@ chat_update_contacts_visibility (EmpathyChat *chat, gboolean show) { EmpathyChatPriv *priv = GET_PRIV (chat); - GtkAllocation allocation; if (!priv->scrolled_window_contacts) { return; @@ -2418,8 +2848,9 @@ chat_update_contacts_visibility (EmpathyChat *chat, } if (show && priv->contact_list_view == NULL) { - EmpathyContactListStore *store; + EmpathyIndividualStore *store; gint min_width; + GtkAllocation allocation; /* We are adding the contact list to the chat, we don't want the * chat view to become too small. If the chat view is already @@ -2430,26 +2861,35 @@ chat_update_contacts_visibility (EmpathyChat *chat, gtk_widget_get_allocation (priv->vbox_left, &allocation); min_width = MIN (allocation.width, 250); gtk_widget_set_size_request (priv->vbox_left, min_width, -1); - g_idle_add (chat_reset_size_request, priv->vbox_left); - if (priv->contacts_width > 0) { - gtk_paned_set_position (GTK_PANED (priv->hpaned), - priv->contacts_width); - } + /* There is no way to know when the window resize will happen + * since it is WM's decision. Let's hope it won't be longer. */ + if (priv->contacts_visible_id != 0) + g_source_remove (priv->contacts_visible_id); + priv->contacts_visible_id = g_timeout_add (500, + chat_contacts_visible_timeout_cb, chat); + + store = EMPATHY_INDIVIDUAL_STORE ( + empathy_individual_store_channel_new ((TpChannel *) priv->tp_chat)); + + empathy_individual_store_set_show_groups (store, FALSE); + + priv->contact_list_view = GTK_WIDGET (empathy_individual_view_new (store, + EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP, + EMPATHY_INDIVIDUAL_FEATURE_ADD_CONTACT | + EMPATHY_INDIVIDUAL_FEATURE_CHAT | + EMPATHY_INDIVIDUAL_FEATURE_CALL | + EMPATHY_INDIVIDUAL_FEATURE_LOG | + EMPATHY_INDIVIDUAL_FEATURE_INFO)); + + empathy_individual_view_set_show_offline ( + EMPATHY_INDIVIDUAL_VIEW (priv->contact_list_view), TRUE); + empathy_individual_view_set_show_uninteresting ( + EMPATHY_INDIVIDUAL_VIEW (priv->contact_list_view), TRUE); - store = empathy_contact_list_store_new ( - EMPATHY_CONTACT_LIST (priv->tp_chat)); - empathy_contact_list_store_set_show_groups ( - EMPATHY_CONTACT_LIST_STORE (store), FALSE); - - priv->contact_list_view = GTK_WIDGET (empathy_contact_list_view_new (store, - EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP, - EMPATHY_CONTACT_FEATURE_CHAT | - EMPATHY_CONTACT_FEATURE_CALL | - EMPATHY_CONTACT_FEATURE_LOG | - EMPATHY_CONTACT_FEATURE_INFO)); gtk_container_add (GTK_CONTAINER (priv->scrolled_window_contacts), priv->contact_list_view); + gtk_widget_show (priv->contact_list_view); gtk_widget_show (priv->scrolled_window_contacts); g_object_unref (store); @@ -2476,6 +2916,32 @@ empathy_chat_set_show_contacts (EmpathyChat *chat, g_object_notify (G_OBJECT (chat), "show-contacts"); } +static void +chat_self_contact_changed_cb (EmpathyChat *chat) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + + if (priv->self_contact != NULL) { + g_signal_handlers_disconnect_by_func (priv->self_contact, + chat_self_contact_alias_changed_cb, + chat); + } + g_clear_object (&priv->self_contact); + + priv->self_contact = empathy_tp_chat_get_self_contact (priv->tp_chat); + if (priv->self_contact != NULL) { + g_object_ref (priv->self_contact); + + if (empathy_chat_is_room (chat)) { + g_signal_connect_swapped (priv->self_contact, "notify::alias", + G_CALLBACK (chat_self_contact_alias_changed_cb), + chat); + } + } + + chat_self_contact_alias_changed_cb (chat); +} + static void chat_remote_contact_changed_cb (EmpathyChat *chat) { @@ -2495,10 +2961,7 @@ chat_remote_contact_changed_cb (EmpathyChat *chat) priv->handle_type = TP_HANDLE_TYPE_CONTACT; } else if (priv->tp_chat != NULL) { - TpChannel *channel; - - channel = empathy_tp_chat_get_channel (priv->tp_chat); - g_object_get (channel, "handle-type", &priv->handle_type, NULL); + tp_channel_get_handle ((TpChannel *) priv->tp_chat, &priv->handle_type); } chat_update_contacts_visibility (chat, priv->show_contacts); @@ -2508,7 +2971,10 @@ chat_remote_contact_changed_cb (EmpathyChat *chat) } static void -chat_destroy_cb (EmpathyTpChat *tp_chat, +chat_invalidated_cb (EmpathyTpChat *tp_chat, + guint domain, + gint code, + gchar *message, EmpathyChat *chat) { EmpathyChatPriv *priv; @@ -2528,6 +2994,8 @@ chat_destroy_cb (EmpathyTpChat *tp_chat, gtk_widget_set_sensitive (chat->input_text_view, FALSE); chat_update_contacts_visibility (chat, FALSE); + + priv->unread_messages_when_offline = priv->unread_messages; } static gboolean @@ -2675,7 +3143,6 @@ chat_create_ui (EmpathyChat *chat) GList *list = NULL; gchar *filename; GtkTextBuffer *buffer; - gint paned_pos; EmpathyThemeManager *theme_mgr; filename = empathy_file_lookup ("empathy-chat.ui", @@ -2728,6 +3195,9 @@ chat_create_ui (EmpathyChat *chat) g_signal_connect (chat->input_text_view, "realize", G_CALLBACK (chat_input_realize_cb), chat); + g_signal_connect (chat->input_text_view, "button-press-event", + G_CALLBACK (chat_input_button_press_event_cb), + chat); g_signal_connect (chat->input_text_view, "populate-popup", G_CALLBACK (chat_input_populate_popup_cb), chat); @@ -2758,12 +3228,6 @@ chat_create_ui (EmpathyChat *chat) G_CALLBACK (chat_hpaned_pos_changed_cb), chat); - /* Load the paned position */ - paned_pos = g_settings_get_int (priv->gsettings_ui, - EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS); - if (paned_pos != 0) - gtk_paned_set_position (GTK_PANED(priv->hpaned), paned_pos); - /* Set widget focus order */ list = g_list_append (NULL, priv->search_bar); list = g_list_append (list, priv->scrolled_window_input); @@ -2781,33 +3245,10 @@ chat_create_ui (EmpathyChat *chat) g_list_free (list); /* Add the main widget in the chat widget */ - gtk_container_add (GTK_CONTAINER (chat), priv->widget); + gtk_box_pack_start (GTK_BOX (chat), priv->widget, TRUE, TRUE, 0); g_object_unref (gui); } -static void -chat_size_allocate (GtkWidget *widget, - GtkAllocation *allocation) -{ - GtkBin *bin = GTK_BIN (widget); - GtkAllocation child_allocation; - GtkWidget *child; - - gtk_widget_set_allocation (widget, allocation); - - child = gtk_bin_get_child (bin); - - if (child && gtk_widget_get_visible (child)) - { - child_allocation.x = allocation->x + gtk_container_get_border_width (GTK_CONTAINER (widget)); - child_allocation.y = allocation->y + gtk_container_get_border_width (GTK_CONTAINER (widget)); - child_allocation.width = MAX (allocation->width - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0); - child_allocation.height = MAX (allocation->height - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0); - - gtk_widget_size_allocate (child, &child_allocation); - } -} - static void chat_finalize (GObject *object) { @@ -2825,6 +3266,9 @@ chat_finalize (GObject *object) if (priv->save_paned_pos_id != 0) g_source_remove (priv->save_paned_pos_id); + if (priv->contacts_visible_id != 0) + g_source_remove (priv->contacts_visible_id); + g_object_unref (priv->gsettings_chat); g_object_unref (priv->gsettings_ui); @@ -2841,25 +3285,37 @@ chat_finalize (GObject *object) if (priv->tp_chat) { g_signal_handlers_disconnect_by_func (priv->tp_chat, - chat_destroy_cb, chat); + chat_invalidated_cb, chat); g_signal_handlers_disconnect_by_func (priv->tp_chat, chat_message_received_cb, chat); + g_signal_handlers_disconnect_by_func (priv->tp_chat, + chat_message_acknowledged_cb, chat); g_signal_handlers_disconnect_by_func (priv->tp_chat, chat_send_error_cb, chat); g_signal_handlers_disconnect_by_func (priv->tp_chat, chat_state_changed_cb, chat); - g_signal_handlers_disconnect_by_func (priv->tp_chat, - chat_property_changed_cb, chat); g_signal_handlers_disconnect_by_func (priv->tp_chat, chat_members_changed_cb, chat); + g_signal_handlers_disconnect_by_func (priv->tp_chat, + chat_self_contact_changed_cb, chat); g_signal_handlers_disconnect_by_func (priv->tp_chat, chat_remote_contact_changed_cb, chat); + g_signal_handlers_disconnect_by_func (priv->tp_chat, + chat_title_changed_cb, chat); + g_signal_handlers_disconnect_by_func (priv->tp_chat, + chat_subject_changed_cb, chat); empathy_tp_chat_leave (priv->tp_chat, ""); g_object_unref (priv->tp_chat); } if (priv->account) { g_object_unref (priv->account); } + if (priv->self_contact) { + g_signal_handlers_disconnect_by_func (priv->self_contact, + chat_self_contact_alias_changed_cb, + chat); + g_object_unref (priv->self_contact); + } if (priv->remote_contact) { g_object_unref (priv->remote_contact); } @@ -2873,6 +3329,8 @@ chat_finalize (GObject *object) g_free (priv->subject); g_completion_free (priv->completion); + tp_clear_pointer (&priv->highlight_regex, g_regex_unref); + G_OBJECT_CLASS (empathy_chat_parent_class)->finalize (object); } @@ -2896,7 +3354,6 @@ chat_constructed (GObject *object) static void empathy_chat_class_init (EmpathyChatClass *klass) { - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = chat_finalize; @@ -2904,8 +3361,6 @@ empathy_chat_class_init (EmpathyChatClass *klass) object_class->set_property = chat_set_property; object_class->constructed = chat_constructed; - widget_class->size_allocate = chat_size_allocate; - g_object_class_install_property (object_class, PROP_TP_CHAT, g_param_spec_object ("tp-chat", @@ -2980,25 +3435,41 @@ empathy_chat_class_init (EmpathyChatClass *klass) 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_NB_UNREAD_MESSAGES, + g_param_spec_uint ("nb-unread-messages", + "Num Unread Messages", + "The number of unread messages", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + signals[COMPOSING] = g_signal_new ("composing", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, - g_cclosure_marshal_VOID__BOOLEAN, + g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + /** + * EmpathyChat::new-message: + * @self: the #EmpathyChat + * @message: the new message + * @pending: whether the message was in the pending queue when @self + * was created + * @should_highlight: %TRUE if the message mentions the local user + */ signals[NEW_MESSAGE] = g_signal_new ("new-message", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, - _empathy_gtk_marshal_VOID__OBJECT_BOOLEAN, + g_cclosure_marshal_generic, G_TYPE_NONE, - 2, EMPATHY_TYPE_MESSAGE, G_TYPE_BOOLEAN); + 3, EMPATHY_TYPE_MESSAGE, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN); signals[PART_COMMAND_ENTERED] = g_signal_new ("part-command-entered", @@ -3006,7 +3477,7 @@ empathy_chat_class_init (EmpathyChatClass *klass) G_SIGNAL_RUN_LAST, 0, NULL, NULL, - g_cclosure_marshal_VOID__POINTER, + g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_STRV); @@ -3033,7 +3504,7 @@ account_manager_prepared_cb (GObject *source_object, EmpathyChat *chat = user_data; GError *error = NULL; - if (!tp_account_manager_prepare_finish (account_manager, result, &error)) { + if (!tp_proxy_prepare_finish (account_manager, result, &error)) { DEBUG ("Failed to prepare the account manager: %s", error->message); g_error_free (error); return; @@ -3062,12 +3533,13 @@ empathy_chat_init (EmpathyChat *chat) priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA); priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA); - priv->contacts_width = -1; + priv->contacts_width = g_settings_get_int (priv->gsettings_ui, + EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS); priv->input_history = NULL; priv->input_history_current = NULL; priv->account_manager = tp_account_manager_dup (); - tp_account_manager_prepare_async (priv->account_manager, NULL, + tp_proxy_prepare_async (priv->account_manager, NULL, account_manager_prepared_cb, chat); priv->show_contacts = g_settings_get_boolean (priv->gsettings_chat, @@ -3173,7 +3645,7 @@ chat_prompt_to_save_password (EmpathyChat *self, gtk_info_bar_set_message_type (GTK_INFO_BAR (data->info_bar), GTK_MESSAGE_QUESTION); - hbox = gtk_hbox_new (FALSE, 5); + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); gtk_box_pack_start (GTK_BOX (content_area), hbox, TRUE, TRUE, 0); /* Add image */ @@ -3223,7 +3695,7 @@ provide_password_cb (GObject *tp_chat, EmpathyChatPriv *priv = GET_PRIV (self); GError *error = NULL; - if (!empathy_tp_chat_provide_password_finish (EMPATHY_TP_CHAT (tp_chat), res, + if (!tp_channel_provide_password_finish (TP_CHANNEL (tp_chat), res, &error)) { DEBUG ("error: %s", error->message); /* FIXME: what should we do if that's another error? Close the channel? @@ -3267,6 +3739,7 @@ provide_password_cb (GObject *tp_chat, /* Room joined */ gtk_widget_set_sensitive (priv->hpaned, TRUE); + gtk_widget_set_sensitive (self->input_text_view, TRUE); gtk_widget_grab_focus (self->input_text_view); } @@ -3286,7 +3759,7 @@ password_infobar_response_cb (GtkWidget *info_bar, password = gtk_entry_get_text (GTK_ENTRY (data->entry)); - empathy_tp_chat_provide_password_async (priv->tp_chat, password, + tp_channel_provide_password_async (TP_CHANNEL (priv->tp_chat), password, provide_password_cb, data); gtk_widget_set_sensitive (data->button, FALSE); @@ -3332,7 +3805,7 @@ password_entry_changed_cb (GtkEditable *entry, } static void -chat_invalidated_cb (TpProxy *proxy, +infobar_chat_invalidated_cb (TpProxy *proxy, guint domain, gint code, gchar *message, @@ -3366,7 +3839,7 @@ display_password_info_bar (EmpathyChat *self) content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar)); - hbox = gtk_hbox_new (FALSE, 5); + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); gtk_box_pack_start (GTK_BOX (content_area), hbox, TRUE, TRUE, 0); /* Add image */ @@ -3425,8 +3898,8 @@ display_password_info_bar (EmpathyChat *self) TRUE, TRUE, 3); gtk_widget_show_all (hbox); - tp_g_signal_connect_object (empathy_tp_chat_get_channel (priv->tp_chat), - "invalidated", G_CALLBACK (chat_invalidated_cb), + tp_g_signal_connect_object (priv->tp_chat, + "invalidated", G_CALLBACK (infobar_chat_invalidated_cb), info_bar, 0); data->response_id = g_signal_connect (info_bar, "response", @@ -3449,7 +3922,7 @@ provide_saved_password_cb (GObject *tp_chat, EmpathyChatPriv *priv = GET_PRIV (self); GError *error = NULL; - if (!empathy_tp_chat_provide_password_finish (EMPATHY_TP_CHAT (tp_chat), res, + if (!tp_channel_provide_password_finish (TP_CHANNEL (tp_chat), res, &error)) { DEBUG ("error: %s", error->message); /* FIXME: what should we do if that's another error? Close the channel? @@ -3489,7 +3962,7 @@ chat_room_got_password_cb (GObject *source, return; } - empathy_tp_chat_provide_password_async (priv->tp_chat, password, + tp_channel_provide_password_async (TP_CHANNEL (priv->tp_chat), password, provide_saved_password_cb, self); } @@ -3498,7 +3971,7 @@ chat_password_needed_changed_cb (EmpathyChat *self) { EmpathyChatPriv *priv = GET_PRIV (self); - if (empathy_tp_chat_password_needed (priv->tp_chat)) { + if (tp_channel_password_needed (TP_CHANNEL (priv->tp_chat))) { empathy_keyring_get_room_password_async (priv->account, empathy_tp_chat_get_id (priv->tp_chat), chat_room_got_password_cb, self); @@ -3510,7 +3983,8 @@ chat_sms_channel_changed_cb (EmpathyChat *self) { EmpathyChatPriv *priv = GET_PRIV (self); - priv->sms_channel = empathy_tp_chat_is_sms_channel (priv->tp_chat); + priv->sms_channel = tp_text_channel_is_sms_channel ( + (TpTextChannel *) priv->tp_chat); g_object_notify (G_OBJECT (self), "sms-channel"); } @@ -3525,11 +3999,9 @@ empathy_chat_set_tp_chat (EmpathyChat *chat, EmpathyTpChat *tp_chat) { EmpathyChatPriv *priv = GET_PRIV (chat); - GPtrArray *properties; g_return_if_fail (EMPATHY_IS_CHAT (chat)); g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat)); - g_return_if_fail (empathy_tp_chat_is_ready (tp_chat)); if (priv->tp_chat) { return; @@ -3542,61 +4014,55 @@ empathy_chat_set_tp_chat (EmpathyChat *chat, priv->tp_chat = g_object_ref (tp_chat); priv->account = g_object_ref (empathy_tp_chat_get_account (priv->tp_chat)); - g_signal_connect (tp_chat, "destroy", - G_CALLBACK (chat_destroy_cb), + g_signal_connect (tp_chat, "invalidated", + G_CALLBACK (chat_invalidated_cb), chat); - g_signal_connect (tp_chat, "message-received", + g_signal_connect (tp_chat, "message-received-empathy", G_CALLBACK (chat_message_received_cb), chat); + g_signal_connect (tp_chat, "message_acknowledged", + G_CALLBACK (chat_message_acknowledged_cb), + chat); g_signal_connect (tp_chat, "send-error", G_CALLBACK (chat_send_error_cb), chat); - g_signal_connect (tp_chat, "chat-state-changed", + g_signal_connect (tp_chat, "chat-state-changed-empathy", G_CALLBACK (chat_state_changed_cb), chat); - g_signal_connect (tp_chat, "property-changed", - G_CALLBACK (chat_property_changed_cb), - chat); g_signal_connect (tp_chat, "members-changed", G_CALLBACK (chat_members_changed_cb), chat); g_signal_connect (tp_chat, "member-renamed", G_CALLBACK (chat_member_renamed_cb), chat); + g_signal_connect_swapped (tp_chat, "notify::self-contact", + G_CALLBACK (chat_self_contact_changed_cb), + chat); g_signal_connect_swapped (tp_chat, "notify::remote-contact", G_CALLBACK (chat_remote_contact_changed_cb), chat); g_signal_connect_swapped (tp_chat, "notify::password-needed", G_CALLBACK (chat_password_needed_changed_cb), chat); - g_signal_connect_swapped (tp_chat, "notify::sms-channel", + g_signal_connect_swapped (tp_chat, "notify::is-sms-channel", G_CALLBACK (chat_sms_channel_changed_cb), chat); g_signal_connect_swapped (tp_chat, "notify::n-messages-sending", G_CALLBACK (chat_n_messages_sending_changed_cb), chat); + g_signal_connect_swapped (tp_chat, "notify::title", + G_CALLBACK (chat_title_changed_cb), + chat); + g_signal_connect_swapped (tp_chat, "notify::subject", + G_CALLBACK (chat_subject_changed_cb), + chat); /* Get initial value of properties */ - properties = empathy_tp_chat_get_properties (priv->tp_chat); - if (properties != NULL) { - guint i; - - for (i = 0; i < properties->len; i++) { - EmpathyTpChatProperty *property; - - property = g_ptr_array_index (properties, i); - if (property->value == NULL) - continue; - - chat_property_changed_cb (priv->tp_chat, - property->name, - property->value, - chat); - } - } - chat_sms_channel_changed_cb (chat); + chat_self_contact_changed_cb (chat); chat_remote_contact_changed_cb (chat); + chat_title_changed_cb (chat); + chat_subject_changed_cb (chat); if (chat->input_text_view) { gtk_widget_set_sensitive (chat->input_text_view, TRUE); @@ -3691,16 +4157,30 @@ empathy_chat_get_contact_menu (EmpathyChat *chat) { EmpathyChatPriv *priv = GET_PRIV (chat); GtkWidget *menu = NULL; + FolksIndividual *individual; + TpContact *contact; g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL); - if (priv->remote_contact) { - menu = empathy_contact_menu_new (priv->remote_contact, - EMPATHY_CONTACT_FEATURE_CALL | - EMPATHY_CONTACT_FEATURE_LOG | - EMPATHY_CONTACT_FEATURE_INFO | - EMPATHY_CONTACT_FEATURE_BLOCK); - } + if (priv->remote_contact == NULL) + return NULL; + + contact = empathy_contact_get_tp_contact (priv->remote_contact); + if (contact == NULL) + return NULL; + + individual = empathy_ensure_individual_from_tp_contact (contact); + + if (individual == NULL) + return NULL; + + menu = empathy_individual_menu_new (individual, + EMPATHY_INDIVIDUAL_FEATURE_CALL | + EMPATHY_INDIVIDUAL_FEATURE_LOG | + EMPATHY_INDIVIDUAL_FEATURE_INFO | + EMPATHY_INDIVIDUAL_FEATURE_BLOCK, NULL); + + g_object_unref (individual); return menu; } @@ -3852,6 +4332,16 @@ empathy_chat_is_room (EmpathyChat *chat) return (priv->handle_type == TP_HANDLE_TYPE_ROOM); } +gboolean +empathy_chat_is_highlighted (EmpathyChat *chat) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + + g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE); + + return priv->highlighted; +} + guint empathy_chat_get_nb_unread_messages (EmpathyChat *self) { @@ -3875,12 +4365,20 @@ empathy_chat_messages_read (EmpathyChat *self) if (priv->retrieving_backlogs) return; - if (priv->tp_chat != NULL ) { - empathy_tp_chat_acknowledge_all_messages (priv->tp_chat); + if (priv->tp_chat != NULL) { + tp_text_channel_ack_all_pending_messages_async ( + TP_TEXT_CHANNEL (priv->tp_chat), NULL, NULL); } - priv->unread_messages = 0; - empathy_chat_view_focus_toggled (self->view, TRUE); + priv->highlighted = FALSE; + + if (priv->unread_messages_when_offline > 0) { + /* We can't ack those as the connection has gone away so just consider + * them as read. */ + priv->unread_messages -= priv->unread_messages_when_offline; + g_object_notify (G_OBJECT (self), "nb-unread-messages"); + priv->unread_messages_when_offline = 0; + } } /* Return TRUE if on of the contacts in this chat is composing */