X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=libempathy-gtk%2Fempathy-chat.c;h=fc35bec887771656d12ce69e1bb53c99dac447ed;hp=bdb97be3a5a10bcea1529cdebabdb496a2b8698c;hb=3483e7f97ed6d20427672fe68ef03a36ddedda47;hpb=7b6b8da406493311445f6c2470a005a542972693 diff --git a/libempathy-gtk/empathy-chat.c b/libempathy-gtk/empathy-chat.c index bdb97be3..fc35bec8 100644 --- a/libempathy-gtk/empathy-chat.c +++ b/libempathy-gtk/empathy-chat.c @@ -2,6 +2,7 @@ /* * Copyright (C) 2002-2007 Imendio AB * Copyright (C) 2007-2010 Collabora Ltd. + * Copyright (C) 2012 Red Hat, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -25,44 +26,34 @@ * Xavier Claessens */ -#include +/* for GCompletion */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS 1 -#include -#include +#include "config.h" +#include "empathy-chat.h" -#undef G_DISABLE_DEPRECATED /* for GCompletion */ -#include #include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "empathy-chat.h" -#include "empathy-spell.h" -#include "empathy-contact-dialogs.h" -#include "empathy-contact-list-store.h" -#include "empathy-contact-list-view.h" -#include "empathy-contact-menu.h" +#include +#include +#include +#include + +#include "empathy-client-factory.h" +#include "empathy-gsettings.h" +#include "empathy-individual-information-dialog.h" +#include "empathy-individual-store-channel.h" +#include "empathy-individual-view.h" #include "empathy-input-text-view.h" +#include "empathy-request-util.h" #include "empathy-search-bar.h" +#include "empathy-spell.h" +#include "empathy-string-parser.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" -#include "extensions/extensions.h" +#include "empathy-utils.h" #define DEBUG_FLAG EMPATHY_DEBUG_CHAT -#include +#include "empathy-debug.h" #define IS_ENTER(v) (v == GDK_KEY_Return || v == GDK_KEY_ISO_Enter || v == GDK_KEY_KP_Enter) #define COMPOSING_STOP_TIMEOUT 5 @@ -74,6 +65,7 @@ struct _EmpathyChatPriv { gchar *id; gchar *name; gchar *subject; + EmpathyContact *self_contact; EmpathyContact *remote_contact; gboolean show_contacts; @@ -81,6 +73,17 @@ struct _EmpathyChatPriv { GSettings *gsettings_ui; TplLogManager *log_manager; + TplLogWalker *log_walker; + /* Are we watching for scrolling movements? */ + gboolean watch_scroll; + /* Maximum page size of the chat->view. */ + guint max_page_size; + /* The offset from the lower edge of the chat->view before it + * expanded to fit in the newly fetched logs. This is to + * restore the chat->view to the page it was on before the + * latest batch of logs were inserted. */ + guint scroll_offset; + TpAccountManager *account_manager; GList *input_history; GList *input_history_current; @@ -124,9 +127,6 @@ struct _EmpathyChatPriv { 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; /* FIXME: retrieving_backlogs flag is a workaround for Bug#610994 and should * be differently handled since it introduces another race condition, which @@ -156,6 +156,14 @@ struct _EmpathyChatPriv { * 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 { @@ -190,6 +198,7 @@ static guint signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE (EmpathyChat, empathy_chat, GTK_TYPE_BOX); +static gboolean chat_scrollable_connect (gpointer user_data); static gboolean update_misspelled_words (gpointer data); static void @@ -277,11 +286,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, @@ -314,7 +325,7 @@ chat_new_connection_cb (TpAccount *account, if (priv->tp_chat != NULL || account != priv->account || priv->handle_type == TP_HANDLE_TYPE_NONE || - EMP_STR_EMPTY (priv->id)) + TPAW_STR_EMPTY (priv->id)) return; g_object_ref (chat); @@ -367,11 +378,18 @@ 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; - set_chat_state (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; } @@ -380,9 +398,16 @@ 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); @@ -700,12 +725,12 @@ chat_command_msg_cb (GObject *source, DEBUG ("Failed to get channel: %s", error->message); g_error_free (error); - empathy_chat_view_append_event (data->chat->view, + empathy_theme_adium_append_event (data->chat->view, _("Failed to open private chat")); goto OUT; } - if (!EMP_STR_EMPTY (data->message) && TP_IS_TEXT_CHANNEL (channel)) { + if (!TPAW_STR_EMPTY (data->message) && TP_IS_TEXT_CHANNEL (channel)) { TpTextChannel *text = (TpTextChannel *) channel; TpMessage *msg; @@ -730,9 +755,9 @@ nick_command_supported (EmpathyChat *chat) EmpathyChatPriv * priv = GET_PRIV (chat); TpConnection *connection; - connection = tp_channel_borrow_connection (TP_CHANNEL (priv->tp_chat)); + connection = tp_channel_get_connection (TP_CHANNEL (priv->tp_chat)); return tp_proxy_has_interface_by_id (connection, - EMP_IFACE_QUARK_CONNECTION_INTERFACE_RENAMING); + TP_IFACE_QUARK_CONNECTION_INTERFACE_RENAMING); } static gboolean @@ -748,7 +773,7 @@ static void chat_command_clear (EmpathyChat *chat, GStrv strv) { - empathy_chat_view_clear (chat->view); + empathy_theme_adium_clear (chat->view); } static void @@ -756,26 +781,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) { - empathy_chat_view_append_event (chat->view, + if (!empathy_tp_chat_supports_subject (priv->tp_chat)) { + empathy_theme_adium_append_event (chat->view, _("Topic not supported on this conversation")); return; } - if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) { - empathy_chat_view_append_event (chat->view, + if (!empathy_tp_chat_can_set_subject (priv->tp_chat)) { + empathy_theme_adium_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 @@ -801,7 +820,7 @@ chat_command_join (EmpathyChat *chat, * https://bugs.freedesktop.org/show_bug.cgi?id=13422 */ while (rooms[i] != NULL) { /* ignore empty strings */ - if (!EMP_STR_EMPTY (rooms[i])) { + if (!TPAW_STR_EMPTY (rooms[i])) { empathy_chat_join_muc (chat, rooms[i]); } i++; @@ -824,17 +843,13 @@ chat_command_msg_internal (EmpathyChat *chat, EmpathyChatPriv *priv = GET_PRIV (chat); ChatCommandMsgData *data; TpAccountChannelRequest *req; - GHashTable *request; - request = tp_asv_new ( - TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT, - TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT, - TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, contact_id, - NULL); - - req = tp_account_channel_request_new (priv->account, request, + req = tp_account_channel_request_new_text (priv->account, empathy_get_current_action_time ()); + tp_account_channel_request_set_target_id (req, TP_HANDLE_TYPE_CONTACT, + contact_id); + /* FIXME: We should probably search in members alias. But this * is enough for IRC */ data = g_slice_new (ChatCommandMsgData); @@ -842,10 +857,9 @@ chat_command_msg_internal (EmpathyChat *chat, data->message = g_strdup (message); tp_account_channel_request_ensure_and_observe_channel_async (req, - EMPATHY_CHAT_BUS_NAME, NULL, chat_command_msg_cb, data); + EMPATHY_CHAT_TP_BUS_NAME, NULL, chat_command_msg_cb, data); g_object_unref (req); - g_hash_table_unref (request); } static void @@ -865,7 +879,7 @@ chat_command_msg (EmpathyChat *chat, } static void -callback_for_request_rename (TpProxy *proxy, +callback_for_request_rename (TpConnection *conn, const GError *error, gpointer user_data, GObject *weak_object) @@ -880,11 +894,11 @@ chat_command_nick (EmpathyChat *chat, GStrv strv) { EmpathyChatPriv *priv = GET_PRIV (chat); - TpProxy *proxy; + TpConnection *conn; - proxy = TP_PROXY (tp_account_get_connection (priv->account)); + conn = tp_account_get_connection (priv->account); - emp_cli_connection_interface_renaming_call_request_rename (proxy, -1, + tp_cli_connection_interface_renaming_call_request_rename (conn, -1, strv[1], callback_for_request_rename, NULL, NULL, NULL); } @@ -901,14 +915,12 @@ chat_command_me (EmpathyChat *chat, 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); @@ -937,65 +949,31 @@ chat_command_say (EmpathyChat *chat, } 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; +whois_got_contact_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + EmpathyChat *chat = user_data; + EmpathyContact *contact; + FolksIndividual *individual; - g_return_if_fail (contacts[0] != NULL); - empathy_contact = empathy_contact_dup_from_tp_contact ( - contacts[0]); + contact = empathy_client_factory_dup_contact_by_id_finish ( + EMPATHY_CLIENT_FACTORY (source), result, NULL); - 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); + if (contact == NULL) { + empathy_theme_adium_append_event (chat->view, _("Invalid contact ID")); + goto out; } + + individual = empathy_ensure_individual_from_tp_contact ( + empathy_contact_get_tp_contact (contact)); + empathy_display_individual_info (individual); + + g_object_unref (individual); + g_object_unref (contact); + +out: + g_object_unref (chat); } static void @@ -1004,14 +982,17 @@ chat_command_whois (EmpathyChat *chat, { EmpathyChatPriv *priv = GET_PRIV (chat); TpConnection *conn; + EmpathyClientFactory *factory; + + conn = tp_channel_get_connection ((TpChannel *) priv->tp_chat); + factory = empathy_client_factory_dup (); - 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)); + /* Element 0 of 'strv' is "whois"; element 1 is the contact ID + * entered by the user (including spaces, if any). */ + empathy_client_factory_dup_contact_by_id_async (factory, conn, strv[1], + whois_got_contact_cb, g_object_ref (chat)); + + g_object_unref (factory); } static void @@ -1124,7 +1105,7 @@ chat_command_show_help (EmpathyChat *chat, } str = g_strdup_printf (_("Usage: %s"), _(item->help)); - empathy_chat_view_append_event (chat->view, str); + empathy_theme_adium_append_event (chat->view, str); g_free (str); } @@ -1146,7 +1127,7 @@ chat_command_help (EmpathyChat *chat, if (commands[i].help == NULL) { continue; } - empathy_chat_view_append_event (chat->view, + empathy_theme_adium_append_event (chat->view, _(commands[i].help)); } return; @@ -1167,7 +1148,7 @@ chat_command_help (EmpathyChat *chat, } } - empathy_chat_view_append_event (chat->view, + empathy_theme_adium_append_event (chat->view, _("Unknown command")); } @@ -1205,7 +1186,7 @@ chat_command_parse (const gchar *text, guint max_parts) /* Append last part if not empty */ item = g_strstrip (g_strdup (text)); - if (!EMP_STR_EMPTY (item)) { + if (!TPAW_STR_EMPTY (item)) { g_ptr_array_add (array, item); DEBUG ("\tITEM: \"%s\"", item); } else { @@ -1233,7 +1214,7 @@ chat_send (EmpathyChat *chat, TpMessage *message; guint i; - if (EMP_STR_EMPTY (msg)) { + if (TPAW_STR_EMPTY (msg)) { return; } @@ -1293,7 +1274,7 @@ chat_send (EmpathyChat *chat, } if (!second_slash) { - empathy_chat_view_append_event (chat->view, + empathy_theme_adium_append_event (chat->view, _("Unknown command; see /help for the available" " commands")); return; @@ -1329,19 +1310,22 @@ chat_input_text_view_send (EmpathyChat *chat) static void chat_state_changed_cb (EmpathyTpChat *tp_chat, - EmpathyContact *contact, + TpContact *tp_contact, TpChannelChatState state, EmpathyChat *chat) { EmpathyChatPriv *priv; GList *l; gboolean was_composing; + EmpathyContact *contact; priv = GET_PRIV (chat); + contact = empathy_contact_dup_from_tp_contact (tp_contact); + if (empathy_contact_is_user (contact)) { /* We don't care about our own chat state */ - return; + goto out; } was_composing = (priv->compositors != NULL); @@ -1386,6 +1370,87 @@ chat_state_changed_cb (EmpathyTpChat *tp_chat, g_signal_emit (chat, signals[COMPOSING], 0, priv->compositors != NULL); } + +out: + g_object_unref (contact); +} + +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; + + 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; + } + + if (empathy_message_is_backlog (message)) { + /* 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 @@ -1403,26 +1468,33 @@ chat_message_received (EmpathyChat *chat, empathy_message_get_supersedes (message), empathy_message_get_body (message)); - empathy_chat_view_edit_message (chat->view, message); + empathy_theme_adium_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); + empathy_theme_adium_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"); } - g_signal_emit (chat, signals[NEW_MESSAGE], 0, message, pending); + g_signal_emit (chat, signals[NEW_MESSAGE], 0, message, pending, + should_highlight); } /* We received a message so the contact is no longer * composing */ - chat_state_changed_cb (priv->tp_chat, sender, + chat_state_changed_cb (priv->tp_chat, empathy_contact_get_tp_contact (sender), TP_CHANNEL_CHAT_STATE_ACTIVE, chat); } @@ -1442,7 +1514,7 @@ chat_message_acknowledged_cb (EmpathyTpChat *tp_chat, { EmpathyChatPriv *priv = GET_PRIV (chat); - empathy_chat_view_message_acknowledged (chat->view, + empathy_theme_adium_message_acknowledged (chat->view, message); if (!empathy_message_is_edit (message)) { @@ -1456,7 +1528,7 @@ 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)); + TpConnection *conn = tp_channel_get_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; @@ -1488,9 +1560,9 @@ append_balance_error (EmpathyChat *chat, } if (str_markup != NULL) - empathy_chat_view_append_event_markup (chat->view, str_markup, str); + empathy_theme_adium_append_event_markup (chat->view, str_markup, str); else - empathy_chat_view_append_event (chat->view, str); + empathy_theme_adium_append_event (chat->view, str); g_free (str); g_free (str_markup); @@ -1546,7 +1618,7 @@ chat_send_error_cb (EmpathyTpChat *tp_chat, str = g_strdup_printf (_("Error sending message: %s"), error); } - empathy_chat_view_append_event (chat->view, str); + empathy_theme_adium_append_event (chat->view, str); g_free (str); } @@ -1584,25 +1656,21 @@ 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)) { + if (TPAW_STR_EMPTY (priv->subject)) { gtk_widget_hide (priv->hbox_topic); } else { gchar *markup_topic; gchar *markup_text; - markup_topic = empathy_add_link_markup (priv->subject); + markup_topic = tpaw_add_link_markup (priv->subject); markup_text = g_strdup_printf ("%s %s", _("Topic:"), markup_topic); @@ -1613,22 +1681,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 { + if (!TPAW_STR_EMPTY (priv->subject)) { + 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_theme_adium_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 @@ -1898,7 +1981,7 @@ chat_input_key_press_event_cb (GtkWidget *widget, } is_start_of_buffer = gtk_text_iter_is_start (&start); - list = empathy_contact_list_get_members (EMPATHY_CONTACT_LIST (priv->tp_chat)); + list = empathy_tp_chat_get_members (priv->tp_chat); g_completion_add_items (priv->completion, list); nick = gtk_text_buffer_get_text (buffer, &start, ¤t, FALSE); @@ -1938,7 +2021,7 @@ chat_input_key_press_event_cb (GtkWidget *widget, g_string_append (message, empathy_contact_get_alias (l->data)); g_string_append (message, " - "); } - empathy_chat_view_append_event (chat->view, message->str); + empathy_theme_adium_append_event (chat->view, message->str); g_string_free (message, TRUE); } @@ -1999,7 +2082,14 @@ chat_input_has_focus_notify_cb (GtkWidget *widget, GParamSpec *pspec, EmpathyChat *chat) { - empathy_chat_view_focus_toggled (chat->view, gtk_widget_has_focus (widget)); + empathy_theme_adium_focus_toggled (chat->view, gtk_widget_has_focus (widget)); +} + +void +empathy_chat_insert_smiley (GtkTextBuffer *buffer, + EmpathySmiley *smiley) +{ + gtk_text_buffer_insert_at_cursor (buffer, smiley->str, -1); } static void @@ -2009,15 +2099,10 @@ chat_insert_smiley_activate_cb (EmpathySmileyManager *manager, { EmpathyChat *chat = EMPATHY_CHAT (user_data); GtkTextBuffer *buffer; - GtkTextIter iter; buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view)); - gtk_text_buffer_get_end_iter (buffer, &iter); - gtk_text_buffer_insert (buffer, &iter, smiley->str, -1); - - gtk_text_buffer_get_end_iter (buffer, &iter); - gtk_text_buffer_insert (buffer, &iter, " ", -1); + empathy_chat_insert_smiley (buffer, smiley); } typedef struct { @@ -2319,7 +2404,7 @@ chat_input_populate_popup_cb (GtkTextView *view, /* Add the Send menu item. */ gtk_text_buffer_get_bounds (buffer, &start, &end); str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); - if (!EMP_STR_EMPTY (str)) { + if (!TPAW_STR_EMPTY (str)) { item = gtk_menu_item_new_with_mnemonic (_("_Send")); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (chat_text_send_cb), chat); @@ -2335,7 +2420,10 @@ chat_input_populate_popup_cb (GtkTextView *view, switch (priv->most_recent_event_type) { case GDK_BUTTON_PRESS: /* get the location from the pointer */ - gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y); + 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, @@ -2363,7 +2451,7 @@ chat_input_populate_popup_cb (GtkTextView *view, str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); } - if (!EMP_STR_EMPTY (str)) { + if (!TPAW_STR_EMPTY (str)) { chat_spell = chat_spell_new (chat, str, start, end); g_object_set_data_full (G_OBJECT (menu), "chat-spell", chat_spell, @@ -2403,10 +2491,11 @@ static gboolean chat_log_filter (TplEvent *event, gpointer user_data) { - EmpathyChat *chat = user_data; - EmpathyMessage *message; + EmpathyChat *chat = EMPATHY_CHAT (user_data); EmpathyChatPriv *priv = GET_PRIV (chat); + EmpathyMessage *message; const GList *pending; + bool retval = FALSE; g_return_val_if_fail (TPL_IS_EVENT (event), FALSE); g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE); @@ -2415,29 +2504,25 @@ chat_log_filter (TplEvent *event, message = empathy_message_from_tpl_log_event (event); for (; pending; pending = g_list_next (pending)) { - if (empathy_message_equal (message, pending->data)) { - g_object_unref (message); - return FALSE; - } + if (empathy_message_equal (message, pending->data)) + goto out; } + retval = TRUE; + +out: g_object_unref (message); - return TRUE; + return retval; } - static void show_pending_messages (EmpathyChat *chat) { EmpathyChatPriv *priv = GET_PRIV (chat); const GList *messages, *l; g_return_if_fail (EMPATHY_IS_CHAT (chat)); - - if (chat->view == NULL || priv->tp_chat == NULL) - return; - - if (!priv->can_show_pending) - return; + g_return_if_fail (chat->view != NULL); + g_return_if_fail (priv->tp_chat != NULL); messages = empathy_tp_chat_get_pending_messages (priv->tp_chat); @@ -2447,9 +2532,28 @@ show_pending_messages (EmpathyChat *chat) { } } +static gboolean +chat_scrollable_set_value (gpointer user_data) +{ + EmpathyChat *chat = EMPATHY_CHAT (user_data); + EmpathyChatPriv *priv = GET_PRIV (chat); + GtkAdjustment *adjustment; + guint upper; + + adjustment = gtk_scrollable_get_vadjustment ( + GTK_SCROLLABLE (chat->view)); + + /* Set the chat->view's adjustment back to the value it had + * before it grew as a result of new logs being inserted. + */ + upper = (guint) gtk_adjustment_get_upper (adjustment); + gtk_adjustment_set_value (adjustment, upper - priv->scroll_offset); + + return G_SOURCE_REMOVE; +} static void -got_filtered_messages_cb (GObject *manager, +got_filtered_messages_cb (GObject *walker, GAsyncResult *result, gpointer user_data) { @@ -2459,16 +2563,16 @@ got_filtered_messages_cb (GObject *manager, EmpathyChatPriv *priv = GET_PRIV (chat); GError *error = NULL; - if (!tpl_log_manager_get_filtered_events_finish (TPL_LOG_MANAGER (manager), + if (!tpl_log_walker_get_events_finish (TPL_LOG_WALKER (walker), result, &messages, &error)) { DEBUG ("%s. Aborting.", error->message); - empathy_chat_view_append_event (chat->view, + empathy_theme_adium_append_event (chat->view, _("Failed to retrieve recent logs")); g_error_free (error); goto out; } - for (l = messages; l; l = g_list_next (l)) { + for (l = g_list_last (messages); l; l = g_list_previous (l)) { EmpathyMessage *message; g_assert (TPL_IS_EVENT (l->data)); @@ -2493,13 +2597,15 @@ got_filtered_messages_cb (GObject *manager, "sender", empathy_message_get_sender (message), NULL); - empathy_chat_view_append_message (chat->view, syn_msg); - empathy_chat_view_edit_message (chat->view, message); + empathy_theme_adium_prepend_message (chat->view, syn_msg, + chat_should_highlight (chat, syn_msg)); + empathy_theme_adium_edit_message (chat->view, message); g_object_unref (syn_msg); } else { /* append the latest message */ - empathy_chat_view_append_message (chat->view, message); + empathy_theme_adium_prepend_message (chat->view, message, + chat_should_highlight (chat, message)); } g_object_unref (message); @@ -2507,50 +2613,149 @@ got_filtered_messages_cb (GObject *manager, g_list_free (messages); out: - /* in case of TPL error, skip backlog and show pending messages */ - priv->can_show_pending = TRUE; - show_pending_messages (chat); - /* FIXME: See Bug#610994, we are forcing the ACK of the queue. See comments * about it in EmpathyChatPriv definition */ priv->retrieving_backlogs = FALSE; empathy_chat_messages_read (chat); /* Turn back on scrolling */ - empathy_chat_view_scroll (chat->view, TRUE); + empathy_theme_adium_scroll (chat->view, TRUE); + + /* We start watching the scrolling movements only after the first + * batch of logs have been fetched. Otherwise, if the + * chat->view's page size is too small the scrollbar might hit + * the upper edge and trigger another batch of logs to be + * fetched. + */ + if (G_UNLIKELY (!priv->watch_scroll && + !tpl_log_walker_is_end (priv->log_walker))) { + priv->watch_scroll = TRUE; + g_idle_add_full (G_PRIORITY_LOW, chat_scrollable_connect, + g_object_ref (chat), g_object_unref); + } + else { + GtkAdjustment *adjustment; + guint upper; + guint value; + + /* The chat->view's adjustment won't change unless we + * return to the main loop. Save the current offset + * from the lower edge (or the upper value of the + * adjustment) so that we can restore it later once the + * adjustment grows. + */ + adjustment = gtk_scrollable_get_vadjustment ( + GTK_SCROLLABLE (chat->view)); + upper = (guint) gtk_adjustment_get_upper (adjustment); + value = (guint) gtk_adjustment_get_value (adjustment); + priv->scroll_offset = upper - value; + + g_idle_add_full (G_PRIORITY_LOW, chat_scrollable_set_value, + g_object_ref (chat), g_object_unref); + } + + g_object_unref (chat); } -static void +static gboolean chat_add_logs (EmpathyChat *chat) { EmpathyChatPriv *priv = GET_PRIV (chat); - TplEntity *target; if (!priv->id) { - return; + return G_SOURCE_REMOVE; } /* Turn off scrolling temporarily */ - empathy_chat_view_scroll (chat->view, FALSE); + empathy_theme_adium_scroll (chat->view, FALSE); - /* Add messages from last conversation */ - if (priv->handle_type == TP_HANDLE_TYPE_ROOM) - target = tpl_entity_new_from_room_id (priv->id); - else - target = tpl_entity_new (priv->id, TPL_ENTITY_CONTACT, NULL, NULL); + tpl_log_walker_get_events_async (priv->log_walker, 5, + got_filtered_messages_cb, g_object_ref (chat)); + + return G_SOURCE_REMOVE; +} + +static void +chat_schedule_logs (EmpathyChat *chat) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + + if (priv->retrieving_backlogs) + return; priv->retrieving_backlogs = TRUE; - tpl_log_manager_get_filtered_events_async (priv->log_manager, - priv->account, - target, - TPL_EVENT_MASK_TEXT, - 5, - chat_log_filter, - chat, - got_filtered_messages_cb, - (gpointer) chat); + g_timeout_add_full (G_PRIORITY_LOW, 500, /* ms */ + (GSourceFunc) chat_add_logs, g_object_ref (chat), g_object_unref); +} - g_object_unref (target); +static void +chat_view_adjustment_changed_cb (GtkAdjustment *adjustment, + gpointer user_data) +{ + EmpathyChat *chat = EMPATHY_CHAT (user_data); + EmpathyChatPriv *priv = GET_PRIV (chat); + guint page_size; + + if (tpl_log_walker_is_end (priv->log_walker)) { + g_signal_handlers_disconnect_by_func (adjustment, + chat_view_adjustment_changed_cb, user_data); + return; + } + + page_size = (guint) gtk_adjustment_get_page_size (adjustment); + if (page_size <= priv->max_page_size) + return; + + /* We need to fetch more logs if the page size of the view + * increases, so that there is no empty space at the top. + */ + if (G_LIKELY (priv->max_page_size != 0)) + chat_schedule_logs (chat); + + priv->max_page_size = page_size; +} + +static void +chat_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, + gpointer user_data) +{ + EmpathyChat *chat = EMPATHY_CHAT (user_data); + EmpathyChatPriv *priv = GET_PRIV (chat); + guint lower; + guint value; + + if (tpl_log_walker_is_end (priv->log_walker)) { + g_signal_handlers_disconnect_by_func (adjustment, + chat_view_adjustment_value_changed_cb, user_data); + return; + } + + lower = (guint) gtk_adjustment_get_lower (adjustment); + value = (guint) gtk_adjustment_get_value (adjustment); + if (value != lower) + return; + + /* Request for more logs to be fetched if the user hit the + * upper edge of the chat->view. + */ + chat_schedule_logs (chat); +} + +static gboolean +chat_scrollable_connect (gpointer user_data) +{ + EmpathyChat *chat = EMPATHY_CHAT (user_data); + GtkAdjustment *adjustment; + + adjustment = gtk_scrollable_get_vadjustment ( + GTK_SCROLLABLE (chat->view)); + + g_signal_connect (adjustment, "changed", + G_CALLBACK (chat_view_adjustment_changed_cb), chat); + g_signal_connect (adjustment, "value-changed", + G_CALLBACK (chat_view_adjustment_value_changed_cb), chat); + + return G_SOURCE_REMOVE; } static gint @@ -2628,7 +2833,7 @@ build_part_message (guint reason, g_string_append_printf (s, _("%s has left the room"), name); } - if (!EMP_STR_EMPTY (message)) { + if (!TPAW_STR_EMPTY (message)) { /* Note to translators: this string is appended to * notifications like "foo has left the room", with the message * given by the user living the room. If this poses a problem, @@ -2665,7 +2870,7 @@ chat_members_changed_cb (EmpathyTpChat *tp_chat, str = build_part_message (reason, name, actor, message); } - empathy_chat_view_append_event (chat->view, str); + empathy_theme_adium_append_event (chat->view, str); g_free (str); } @@ -2687,7 +2892,7 @@ chat_member_renamed_cb (EmpathyTpChat *tp_chat, str = g_strdup_printf (_("%s is now known as %s"), empathy_contact_get_alias (old_contact), empathy_contact_get_alias (new_contact)); - empathy_chat_view_append_event (chat->view, str); + empathy_theme_adium_append_event (chat->view, str); g_free (str); } @@ -2729,7 +2934,7 @@ chat_update_contacts_visibility (EmpathyChat *chat, } if (show && priv->contact_list_view == NULL) { - EmpathyContactListStore *store; + EmpathyIndividualStore *store; gint min_width; GtkAllocation allocation; @@ -2750,19 +2955,27 @@ chat_update_contacts_visibility (EmpathyChat *chat, priv->contacts_visible_id = g_timeout_add (500, chat_contacts_visible_timeout_cb, chat); - 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)); + 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); + 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); @@ -2789,6 +3002,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) { @@ -2837,7 +3076,7 @@ chat_invalidated_cb (EmpathyTpChat *tp_chat, priv->tp_chat = NULL; g_object_notify (G_OBJECT (chat), "tp-chat"); - empathy_chat_view_append_event (chat->view, _("Disconnected")); + empathy_theme_adium_append_event (chat->view, _("Disconnected")); gtk_widget_set_sensitive (chat->input_text_view, FALSE); chat_update_contacts_visibility (chat, FALSE); @@ -2994,7 +3233,7 @@ chat_create_ui (EmpathyChat *chat) filename = empathy_file_lookup ("empathy-chat.ui", "libempathy-gtk"); - gui = empathy_builder_get_file (filename, + gui = tpaw_builder_get_file (filename, "chat_widget", &priv->widget, "hpaned", &priv->hpaned, "vbox_left", &priv->vbox_left, @@ -3007,7 +3246,7 @@ chat_create_ui (EmpathyChat *chat) "info_bar_vbox", &priv->info_bar_vbox, NULL); - empathy_builder_connect (gui, chat, + tpaw_builder_connect (gui, chat, "expander_topic", "notify::expanded", chat_topic_expander_activate_cb, "label_topic", "size-allocate", chat_topic_label_size_allocate_cb, NULL); @@ -3129,6 +3368,7 @@ chat_finalize (GObject *object) g_object_unref (priv->account_manager); g_object_unref (priv->log_manager); + g_object_unref (priv->log_walker); if (priv->tp_chat) { g_signal_handlers_disconnect_by_func (priv->tp_chat, @@ -3141,18 +3381,28 @@ chat_finalize (GObject *object) 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); } @@ -3166,6 +3416,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); } @@ -3174,16 +3426,37 @@ chat_constructed (GObject *object) { EmpathyChat *chat = EMPATHY_CHAT (object); EmpathyChatPriv *priv = GET_PRIV (chat); + TplEntity *target; + + if (priv->tp_chat != NULL) { + TpChannel *channel = TP_CHANNEL (priv->tp_chat); + TpConnection *conn = tp_channel_get_connection (channel); + gboolean supports_avatars = + tp_proxy_has_interface_by_id (conn, + TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS); + + empathy_theme_adium_set_show_avatars (chat->view, + supports_avatars); + } + + /* Add messages from last conversations. Backlog messages are always + * prepended and pending messages are appended, so we can do both + * independently. Hacks like we previously had for bug #603980 are no + * longer needed. Pending messages are handled within + * empathy_chat_set_tp_chat() so we don't have to care about them here. + */ + if (priv->handle_type == TP_HANDLE_TYPE_ROOM) + target = tpl_entity_new_from_room_id (priv->id); + else + target = tpl_entity_new (priv->id, TPL_ENTITY_CONTACT, NULL, NULL); + + priv->log_walker = tpl_log_manager_walk_filtered_events (priv->log_manager, priv->account, target, + TPL_EVENT_MASK_TEXT, chat_log_filter, chat); + g_object_unref (target); if (priv->handle_type != TP_HANDLE_TYPE_ROOM) { - /* First display logs from the logger and then display pending messages */ chat_add_logs (chat); } - else { - /* Just display pending messages for rooms */ - priv->can_show_pending = TRUE; - show_pending_messages (chat); - } } static void @@ -3288,6 +3561,14 @@ empathy_chat_class_init (EmpathyChatClass *klass) 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), @@ -3296,7 +3577,7 @@ empathy_chat_class_init (EmpathyChatClass *klass) NULL, NULL, 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", @@ -3337,7 +3618,7 @@ account_manager_prepared_cb (GObject *source_object, return; } - accounts = tp_account_manager_get_valid_accounts (account_manager); + accounts = tp_account_manager_dup_valid_accounts (account_manager); for (l = accounts; l != NULL; l = l->next) { TpAccount *account = l->data; @@ -3346,7 +3627,7 @@ account_manager_prepared_cb (GObject *source_object, chat, 0); } - g_list_free (accounts); + g_list_free_full (accounts, g_object_unref); } static void @@ -3381,6 +3662,9 @@ empathy_chat_init (EmpathyChat *chat) priv->completion = g_completion_new ((GCompletionFunc) empathy_contact_get_alias); g_completion_set_compare (priv->completion, chat_contacts_completion_func); + /* Create UI early so by the time empathy_chat_set_tp_chat() is called + * (construct property) the view will already exists to receive pending + * messages. */ chat_create_ui (chat); } @@ -3435,7 +3719,7 @@ remember_password_infobar_response_cb (GtkWidget *info_bar, if (response_id == GTK_RESPONSE_OK) { DEBUG ("Saving room password"); - empathy_keyring_set_room_password_async (priv->account, + tpaw_keyring_set_room_password_async (priv->account, empathy_tp_chat_get_id (priv->tp_chat), data->password, NULL, NULL); @@ -3472,7 +3756,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 */ @@ -3527,7 +3811,7 @@ provide_password_cb (GObject *tp_chat, DEBUG ("error: %s", error->message); /* FIXME: what should we do if that's another error? Close the channel? * Display the raw D-Bus error to the user isn't very useful */ - if (g_error_matches (error, TP_ERRORS, TP_ERROR_AUTHENTICATION_FAILED)) { + if (g_error_matches (error, TP_ERROR, TP_ERROR_AUTHENTICATION_FAILED)) { /* entry */ gtk_entry_set_text (GTK_ENTRY (data->entry), ""); gtk_widget_set_sensitive (data->entry, TRUE); @@ -3555,14 +3839,8 @@ provide_password_cb (GObject *tp_chat, return; } - if (empathy_keyring_is_available ()) { - /* ask whether they want to save the password */ - chat_prompt_to_save_password (self, data); - } else { - /* Get rid of the password info bar finally */ - gtk_widget_destroy (data->info_bar); - g_slice_free (PasswordData, data); - } + /* ask whether they want to save the password */ + chat_prompt_to_save_password (self, data); /* Room joined */ gtk_widget_set_sensitive (priv->hpaned, TRUE); @@ -3628,7 +3906,7 @@ password_entry_changed_cb (GtkEditable *entry, str = gtk_entry_get_text (GTK_ENTRY (entry)); gtk_entry_set_icon_sensitive (GTK_ENTRY (entry), - GTK_ENTRY_ICON_SECONDARY, !EMP_STR_EMPTY (str)); + GTK_ENTRY_ICON_SECONDARY, !TPAW_STR_EMPTY (str)); } static void @@ -3666,7 +3944,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 */ @@ -3754,7 +4032,7 @@ provide_saved_password_cb (GObject *tp_chat, DEBUG ("error: %s", error->message); /* FIXME: what should we do if that's another error? Close the channel? * Display the raw D-Bus error to the user isn't very useful */ - if (g_error_matches (error, TP_ERRORS, TP_ERROR_AUTHENTICATION_FAILED)) { + if (g_error_matches (error, TP_ERROR, TP_ERROR_AUTHENTICATION_FAILED)) { display_password_info_bar (self); gtk_widget_set_sensitive (priv->hpaned, FALSE); } @@ -3777,7 +4055,7 @@ chat_room_got_password_cb (GObject *source, const gchar *password; GError *error = NULL; - password = empathy_keyring_get_room_password_finish (priv->account, + password = tpaw_keyring_get_room_password_finish (priv->account, result, &error); if (error != NULL) { @@ -3799,7 +4077,7 @@ chat_password_needed_changed_cb (EmpathyChat *self) EmpathyChatPriv *priv = GET_PRIV (self); if (tp_channel_password_needed (TP_CHANNEL (priv->tp_chat))) { - empathy_keyring_get_room_password_async (priv->account, + tpaw_keyring_get_room_password_async (priv->account, empathy_tp_chat_get_id (priv->tp_chat), chat_room_got_password_cb, self); } @@ -3826,7 +4104,6 @@ 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)); @@ -3854,18 +4131,18 @@ empathy_chat_set_tp_chat (EmpathyChat *chat, g_signal_connect (tp_chat, "send-error", G_CALLBACK (chat_send_error_cb), chat); - g_signal_connect (tp_chat, "chat-state-changed-empathy", + g_signal_connect (tp_chat, "contact-chat-state-changed", 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); @@ -3878,33 +4155,24 @@ empathy_chat_set_tp_chat (EmpathyChat *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); if (priv->block_events_timeout_id == 0) { - empathy_chat_view_append_event (chat->view, _("Connected")); + empathy_theme_adium_append_event (chat->view, _("Connected")); } } @@ -3912,9 +4180,6 @@ empathy_chat_set_tp_chat (EmpathyChat *chat, g_object_notify (G_OBJECT (chat), "id"); g_object_notify (G_OBJECT (chat), "account"); - /* This is a noop when tp-chat is set at object construction time and causes - * the pending messages to be show when it's set on the object after it has - * been created */ show_pending_messages (chat); /* check if a password is needed */ @@ -3994,16 +4259,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, NULL, + EMPATHY_INDIVIDUAL_FEATURE_CALL | + EMPATHY_INDIVIDUAL_FEATURE_LOG | + EMPATHY_INDIVIDUAL_FEATURE_INFO | + EMPATHY_INDIVIDUAL_FEATURE_BLOCK, NULL); + + g_object_unref (individual); return menu; } @@ -4013,7 +4292,7 @@ empathy_chat_clear (EmpathyChat *chat) { g_return_if_fail (EMPATHY_IS_CHAT (chat)); - empathy_chat_view_clear (chat->view); + empathy_theme_adium_clear (chat->view); } void @@ -4021,7 +4300,7 @@ empathy_chat_scroll_down (EmpathyChat *chat) { g_return_if_fail (EMPATHY_IS_CHAT (chat)); - empathy_chat_view_scroll_down (chat->view); + empathy_theme_adium_scroll_down (chat->view); } void @@ -4041,51 +4320,72 @@ empathy_chat_cut (EmpathyChat *chat) } } -void -empathy_chat_copy (EmpathyChat *chat) +static gboolean +copy_from_chat_view (EmpathyChat *chat) +{ + if (!empathy_theme_adium_get_has_selection (chat->view)) + return FALSE; + + empathy_theme_adium_copy_clipboard (chat->view); + return TRUE; +} + +static gboolean +copy_from_input (EmpathyChat *chat) { GtkTextBuffer *buffer; + GtkClipboard *clipboard; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view)); + if (!gtk_text_buffer_get_has_selection (buffer)) + return FALSE; + + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + gtk_text_buffer_copy_clipboard (buffer, clipboard); + return TRUE; +} + +static gboolean +copy_from_topic (EmpathyChat *chat) +{ + EmpathyChatPriv *priv = GET_PRIV (chat); + gint start_offset; + gint end_offset; + gchar *start; + gchar *end; + gchar *selection; + const gchar *topic; + GtkClipboard *clipboard; + + if (!gtk_label_get_selection_bounds (GTK_LABEL (priv->label_topic), + &start_offset, + &end_offset)) + return FALSE; + topic = gtk_label_get_text (GTK_LABEL (priv->label_topic)); + start = g_utf8_offset_to_pointer (topic, start_offset); + end = g_utf8_offset_to_pointer (topic, end_offset); + selection = g_strndup (start, end - start); + + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text (clipboard, selection, -1); + + g_free (selection); + return TRUE; +} + +void +empathy_chat_copy (EmpathyChat *chat) +{ g_return_if_fail (EMPATHY_IS_CHAT (chat)); - if (empathy_chat_view_get_has_selection (chat->view)) { - empathy_chat_view_copy_clipboard (chat->view); + if (copy_from_chat_view (chat)) return; - } - buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view)); - if (gtk_text_buffer_get_has_selection (buffer)) { - GtkClipboard *clipboard; - - clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + if (copy_from_input (chat)) + return; - gtk_text_buffer_copy_clipboard (buffer, clipboard); - } - else { - gint start_offset; - gint end_offset; - EmpathyChatPriv *priv = GET_PRIV (chat); - - if (gtk_label_get_selection_bounds (GTK_LABEL (priv->label_topic), - &start_offset, - &end_offset)) { - gchar *start; - gchar *end; - gchar *selection; - const gchar *topic; - GtkClipboard *clipboard; - - topic = gtk_label_get_text (GTK_LABEL (priv->label_topic)); - start = g_utf8_offset_to_pointer (topic, start_offset); - end = g_utf8_offset_to_pointer (topic, end_offset); - selection = g_strndup (start, end - start); - - clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); - gtk_clipboard_set_text (clipboard, selection, -1); - - g_free (selection); - } - } + copy_from_topic (chat); } void @@ -4155,6 +4455,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) { @@ -4183,6 +4493,8 @@ empathy_chat_messages_read (EmpathyChat *self) TP_TEXT_CHANNEL (priv->tp_chat), NULL, NULL); } + 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. */