X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=libempathy-gtk%2Fempathy-chat.c;h=d273a3792536b161095434f2e5c31d7f4a052478;hp=5cee9eb0bb139fd8664b48acea9aa542d8c2e6bf;hb=9b3306a89a8419217e9c2350d7c32c1a83704eca;hpb=e0dd22a058f51c159937ebf9a4af7edbd386b02f diff --git a/libempathy-gtk/empathy-chat.c b/libempathy-gtk/empathy-chat.c index 5cee9eb0..d273a379 100644 --- a/libempathy-gtk/empathy-chat.c +++ b/libempathy-gtk/empathy-chat.c @@ -49,13 +49,12 @@ #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 "empathy-gtk-marshal.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" @@ -74,6 +73,7 @@ struct _EmpathyChatPriv { gchar *id; gchar *name; gchar *subject; + EmpathyContact *self_contact; EmpathyContact *remote_contact; gboolean show_contacts; @@ -106,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; @@ -121,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; @@ -148,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 { @@ -180,7 +196,7 @@ enum { 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); @@ -269,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, @@ -359,11 +377,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; } @@ -372,9 +397,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); @@ -748,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 @@ -893,14 +919,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); @@ -1006,6 +1030,44 @@ chat_command_whois (EmpathyChat *chat, 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); @@ -1060,6 +1122,11 @@ static ChatCommandItem commands[] = { {"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 @@ -1068,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); @@ -1088,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; } @@ -1101,6 +1175,9 @@ chat_command_help (EmpathyChat *chat, break; } } + if (commands[i].help == NULL) { + break; + } chat_command_show_help (chat, &commands[i]); return; } @@ -1327,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, @@ -1344,19 +1501,26 @@ chat_message_received (EmpathyChat *chat, 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); + 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"); } - 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 @@ -1384,8 +1548,55 @@ chat_message_acknowledged_cb (EmpathyTpChat *tp_chat, empathy_chat_view_message_acknowledged (chat->view, message); - priv->unread_messages--; - g_object_notify (G_OBJECT (chat), "nb-unread-messages"); + 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 @@ -1399,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"); } @@ -1477,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)) { @@ -1506,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 @@ -1699,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 || @@ -2154,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; @@ -2209,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)) { @@ -2352,13 +2610,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_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); + empathy_chat_view_append_message (chat->view, message, + chat_should_highlight (chat, message)); } g_object_unref (message); @@ -2553,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; } @@ -2565,7 +2838,6 @@ chat_update_contacts_visibility (EmpathyChat *chat, gboolean show) { EmpathyChatPriv *priv = GET_PRIV (chat); - GtkAllocation allocation; if (!priv->scrolled_window_contacts) { return; @@ -2576,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 @@ -2588,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); @@ -2634,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) { @@ -2686,6 +2994,8 @@ chat_invalidated_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 @@ -2833,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", @@ -2886,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); @@ -2916,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); @@ -2939,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) { @@ -2983,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); @@ -3008,18 +3294,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); } @@ -3033,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); } @@ -3056,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; @@ -3064,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", @@ -3154,19 +3449,27 @@ empathy_chat_class_init (EmpathyChatClass *klass) 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", @@ -3174,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); @@ -3201,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; @@ -3230,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, @@ -3341,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 */ @@ -3391,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? @@ -3455,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); @@ -3535,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 */ @@ -3618,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? @@ -3658,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); } @@ -3667,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); @@ -3695,7 +3999,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)); @@ -3726,15 +4029,15 @@ empathy_chat_set_tp_chat (EmpathyChat *chat, 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); @@ -3747,28 +4050,19 @@ 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); @@ -3863,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; } @@ -4024,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) { @@ -4048,7 +4366,18 @@ empathy_chat_messages_read (EmpathyChat *self) return; if (priv->tp_chat != NULL) { - empathy_tp_chat_acknowledge_all_messages (priv->tp_chat); + tp_text_channel_ack_all_pending_messages_async ( + 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. */ + priv->unread_messages -= priv->unread_messages_when_offline; + g_object_notify (G_OBJECT (self), "nb-unread-messages"); + priv->unread_messages_when_offline = 0; } }