]> git.0d.be Git - empathy.git/commitdiff
Merge branch 'remember-my-message-yo'
authorJonny Lamb <jonnylamb@gnome.org>
Wed, 18 May 2011 13:26:08 +0000 (14:26 +0100)
committerJonny Lamb <jonnylamb@gnome.org>
Wed, 18 May 2011 13:26:08 +0000 (14:26 +0100)
12 files changed:
libempathy-gtk/empathy-chat-view.c
libempathy-gtk/empathy-chat-view.h
libempathy-gtk/empathy-chat.c
libempathy-gtk/empathy-contact-widget.c
libempathy-gtk/empathy-theme-adium.c
libempathy/empathy-message.c
libempathy/empathy-message.h
libempathy/empathy-time.c
libempathy/empathy-time.h
libempathy/empathy-tp-chat.c
po/es.po
src/empathy-chat-window.c

index 097215cfdfd615e9800b6b352191dd5c038fd89c..43d89dd78a090dc5a5858252d1c67c1d6cddb157 100644 (file)
@@ -212,3 +212,14 @@ empathy_chat_view_focus_toggled (EmpathyChatView *view,
        }
 }
 
+void
+empathy_chat_view_message_acknowledged (EmpathyChatView *view,
+                                       EmpathyMessage  *message)
+{
+       g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
+
+       if (EMPATHY_TYPE_CHAT_VIEW_GET_IFACE (view)->message_acknowledged) {
+               EMPATHY_TYPE_CHAT_VIEW_GET_IFACE (view)->message_acknowledged (view, message);
+       }
+}
+
index 1af0721a3c3cfe7fe03fc741839af0c325e4945c..73245c4228f1bbd88a2a182174eef0ed1b9d9820 100644 (file)
@@ -70,6 +70,8 @@ struct _EmpathyChatViewIface {
        void             (*copy_clipboard)       (EmpathyChatView *view);
        void             (*focus_toggled)        (EmpathyChatView *view,
                                                  gboolean         has_focus);
+       void             (*message_acknowledged) (EmpathyChatView *view,
+                                                 EmpathyMessage  *message);
 };
 
 GType            empathy_chat_view_get_type             (void) G_GNUC_CONST;
@@ -101,6 +103,8 @@ void             empathy_chat_view_highlight            (EmpathyChatView *view,
 void             empathy_chat_view_copy_clipboard       (EmpathyChatView *view);
 void             empathy_chat_view_focus_toggled        (EmpathyChatView *view,
                                                         gboolean         has_focus);
+void             empathy_chat_view_message_acknowledged (EmpathyChatView *view,
+                                                        EmpathyMessage  *message);
 
 G_END_DECLS
 
index 25203b46eb5e40bf83be672294b4dcf5de11cdc5..79411e74d8dcd94e18f39504b18ee1dc86e165ce 100644 (file)
@@ -47,6 +47,7 @@
 
 #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"
@@ -173,6 +174,7 @@ enum {
        PROP_SHOW_CONTACTS,
        PROP_SMS_CHANNEL,
        PROP_N_MESSAGES_SENDING,
+       PROP_NB_UNREAD_MESSAGES,
 };
 
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -219,6 +221,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;
@@ -905,6 +911,84 @@ 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 = empathy_tp_chat_get_connection (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_help (EmpathyChat *chat, GStrv strv);
 
 typedef void (*ChatCommandFunc) (EmpathyChat *chat, GStrv strv);
@@ -953,6 +1037,9 @@ 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 <contact ID>: display information about a contact")},
+
        {"help", 1, 2, chat_command_help, NULL,
         N_("/help [<command>]: show all supported commands. "
            "If <command> is defined, show its usage.")},
@@ -1244,7 +1331,11 @@ chat_message_received (EmpathyChat *chat,
                               TP_CHANNEL_CHAT_STATE_ACTIVE,
                               chat);
 
-       priv->unread_messages++;
+       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);
 }
 
@@ -1256,6 +1347,20 @@ 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);
+
+       priv->unread_messages--;
+       g_object_notify (G_OBJECT (chat), "nb-unread-messages");
+}
+
 static void
 chat_send_error_cb (EmpathyTpChat          *tp_chat,
                    const gchar            *message_body,
@@ -2844,6 +2949,8 @@ chat_finalize (GObject *object)
                        chat_destroy_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,
@@ -2980,6 +3087,14 @@ 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),
@@ -3548,6 +3663,9 @@ empathy_chat_set_tp_chat (EmpathyChat   *chat,
        g_signal_connect (tp_chat, "message-received",
                          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);
@@ -3875,12 +3993,9 @@ empathy_chat_messages_read (EmpathyChat *self)
        if (priv->retrieving_backlogs)
                return;
 
-       if (priv->tp_chat != NULL ) {
+       if (priv->tp_chat != NULL) {
                empathy_tp_chat_acknowledge_all_messages (priv->tp_chat);
        }
-       priv->unread_messages = 0;
-
-       empathy_chat_view_focus_toggled (self->view, TRUE);
 }
 
 /* Return TRUE if on of the contacts in this chat is composing */
index c4e3748b47e92a78782d45906e7d1c9dd89e17b6..14042bf61f5bd0eebdb261a5cf8cff981c0141dc 100644 (file)
@@ -40,6 +40,7 @@
 #include <libempathy/empathy-contact-manager.h>
 #include <libempathy/empathy-contact-list.h>
 #include <libempathy/empathy-location.h>
+#include <libempathy/empathy-request-util.h>
 #include <libempathy/empathy-time.h>
 #include <libempathy/empathy-utils.h>
 
@@ -283,20 +284,74 @@ contact_widget_bday_changed_cb (GtkCalendar *calendar,
 
 static void contact_widget_details_notify_cb (EmpathyContactWidget *information);
 
+typedef gchar * (* FieldFormatFunc) (GStrv);
+
 typedef struct
 {
   const gchar *field_name;
   const gchar *title;
-  gboolean linkify;
+  FieldFormatFunc format;
 } InfoFieldData;
 
+static gchar *
+linkify_first_value (GStrv values)
+{
+  return empathy_add_link_markup (values[0]);
+}
+
+static gchar *
+format_idle_time (GStrv values)
+{
+  const gchar *value = values[0];
+  int duration = strtol (value, NULL, 10);
+
+  if (duration <= 0)
+    return NULL;
+
+  return empathy_duration_to_string (duration);
+}
+
+static gchar *
+format_server (GStrv values)
+{
+  g_assert (values[0] != NULL);
+
+  if (values[1] == NULL)
+    return g_markup_escape_text (values[0], -1);
+  else
+    return g_markup_printf_escaped ("%s (%s)", values[0], values[1]);
+}
+
+static gchar *
+presence_hack (GStrv values)
+{
+  if (tp_str_empty (values[0]))
+    return NULL;
+
+  return g_markup_escape_text (values[0], -1);
+}
+
 static InfoFieldData info_field_datas[] =
 {
-  { "fn",    N_("Full name:"),      FALSE },
-  { "tel",   N_("Phone number:"),   FALSE },
-  { "email", N_("E-mail address:"), TRUE },
-  { "url",   N_("Website:"),        TRUE },
-  { "bday",  N_("Birthday:"),       FALSE },
+  { "fn",    N_("Full name:"),      NULL },
+  { "tel",   N_("Phone number:"),   NULL },
+  { "email", N_("E-mail address:"), linkify_first_value },
+  { "url",   N_("Website:"),        linkify_first_value },
+  { "bday",  N_("Birthday:"),       NULL },
+
+  /* Note to translators: this is the caption for a string of the form "5
+   * minutes ago", and refers to the time since the contact last interacted
+   * with their IM client.
+   */
+  { "x-idle-time", N_("Last seen:"), format_idle_time },
+  { "x-irc-server", N_("Server:"), format_server },
+  { "x-host", N_("Connected from:"), format_server },
+
+  /* FIXME: once Idle implements SimplePresence using this information, we can
+   * and should bin this.
+   */
+  { "x-presence-status-message", N_("Away message:"), presence_hack },
+
   { NULL, NULL }
 };
 
@@ -526,12 +581,73 @@ contact_widget_details_update_edit (EmpathyContactWidget *information)
   return n_rows;
 }
 
+static gboolean
+channel_name_activated_cb (
+    GtkLabel *label,
+    gchar *uri,
+    EmpathyContactWidget *information)
+{
+  TpAccount *account = empathy_contact_get_account (information->contact);
+
+  empathy_join_muc (account, uri, empathy_get_current_action_time ());
+  return TRUE;
+}
+
+static void
+add_channel_list (
+    EmpathyContactWidget *information,
+    GPtrArray *channels,
+    guint row)
+{
+  GtkWidget *w;
+  GString *label_markup = g_string_new ("");
+  guint i;
+
+  w = gtk_label_new (_("Channels:"));
+  gtk_table_attach (GTK_TABLE (information->table_details),
+      w, 0, 1, row, row + 1, GTK_FILL, 0, 0, 0);
+  gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
+  gtk_widget_show (w);
+
+  for (i = 0; i < channels->len; i++)
+    {
+      const gchar *channel_name = g_ptr_array_index (channels, i);
+      /* We abuse the URI of the link to hold the channel name. It seems to
+       * be okay to just use it essentially verbatim, rather than trying to
+       * ensure it's actually a valid URI.  g_string_append_uri_escaped()
+       * escapes way more than we actually need to; so we're just using
+       * g_markup_escape_text directly.
+       */
+      gchar *escaped = g_markup_escape_text (channel_name, -1);
+
+      if (i > 0)
+        g_string_append (label_markup, ", ");
+
+      g_string_append_printf (label_markup, "<a href='%s'>%s</a>",
+          escaped, channel_name);
+      g_free (escaped);
+    }
+
+  w = gtk_label_new (NULL);
+  gtk_label_set_markup (GTK_LABEL (w), label_markup->str);
+  gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
+  g_signal_connect (w, "activate-link",
+      (GCallback) channel_name_activated_cb, information);
+  gtk_table_attach_defaults (GTK_TABLE (information->table_details),
+      w, 1, 2, row, row + 1);
+  gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
+  gtk_widget_show (w);
+
+  g_string_free (label_markup, TRUE);
+}
+
 static guint
 contact_widget_details_update_show (EmpathyContactWidget *information)
 {
   TpContact *contact;
   GList *info, *l;
   guint n_rows = 0;
+  GPtrArray *channels = g_ptr_array_new ();
 
   contact = empathy_contact_get_tp_contact (information->contact);
   info = tp_contact_get_contact_info (contact);
@@ -541,6 +657,7 @@ contact_widget_details_update_show (EmpathyContactWidget *information)
       TpContactInfoField *field = l->data;
       InfoFieldData *field_data;
       const gchar *value;
+      gchar *markup = NULL;
       GtkWidget *w;
 
       if (field->field_value == NULL || field->field_value[0] == NULL)
@@ -548,6 +665,12 @@ contact_widget_details_update_show (EmpathyContactWidget *information)
 
       value = field->field_value[0];
 
+      if (!tp_strdiff (field->field_name, "x-irc-channel"))
+        {
+          g_ptr_array_add (channels, (gpointer) field->field_value[0]);
+          continue;
+        }
+
       field_data = find_info_field_data (field->field_name);
       if (field_data == NULL)
         {
@@ -555,6 +678,18 @@ contact_widget_details_update_show (EmpathyContactWidget *information)
           continue;
         }
 
+      if (field_data->format != NULL)
+        {
+          markup = field_data->format (field->field_value);
+
+          if (markup == NULL)
+            {
+              DEBUG ("Invalid value for field '%s' (first element was '%s')",
+                  field->field_name, field->field_value[0]);
+              continue;
+            }
+        }
+
       /* Add Title */
       w = gtk_label_new (_(field_data->title));
       gtk_table_attach (GTK_TABLE (information->table_details),
@@ -564,11 +699,8 @@ contact_widget_details_update_show (EmpathyContactWidget *information)
 
       /* Add Value */
       w = gtk_label_new (value);
-      if (field_data->linkify)
+      if (markup != NULL)
         {
-          gchar *markup;
-
-          markup = empathy_add_link_markup (value);
           gtk_label_set_markup (GTK_LABEL (w), markup);
           g_free (markup);
         }
@@ -585,6 +717,13 @@ contact_widget_details_update_show (EmpathyContactWidget *information)
     }
   g_list_free (info);
 
+  if (channels->len > 0)
+    {
+      add_channel_list (information, channels, n_rows);
+      n_rows++;
+    }
+
+  g_ptr_array_unref (channels);
   return n_rows;
 }
 
index 06410c528a8c3a28c65cf20d76c8383dc9abd39f..b62b017c9b18f77b744ce3384f877af40abdf976 100644 (file)
@@ -61,6 +61,9 @@ typedef struct {
        guint                 pages_loading;
        /* Queue of GValue* containing an EmpathyMessage or string */
        GQueue                message_queue;
+       /* Queue of owned gchar* of message token to remove unread
+        * marker for when we lose focus. */
+       GQueue                acked_messages;
        GtkWidget            *inspector_window;
        GSettings            *gsettings_chat;
        gboolean              has_focus;
@@ -569,32 +572,10 @@ theme_adium_append_event_escaped (EmpathyChatView *view,
 }
 
 static void
-theme_adium_remove_focus_marks (EmpathyThemeAdium *theme)
+theme_adium_remove_focus_marks (EmpathyThemeAdium *theme,
+    WebKitDOMNodeList *nodes)
 {
-       EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
-       WebKitDOMDocument *dom;
-       WebKitDOMNodeList *nodes;
        guint i;
-       GError *error = NULL;
-
-       if (!priv->has_unread_message)
-               return;
-
-       priv->has_unread_message = FALSE;
-
-       dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (theme));
-       if (dom == NULL) {
-               return;
-       }
-
-       /* Get all nodes with focus class */
-       nodes = webkit_dom_document_query_selector_all (dom, ".focus", &error);
-       if (nodes == NULL) {
-               DEBUG ("Error getting focus nodes: %s",
-                       error ? error->message : "No error");
-               g_clear_error (&error);
-               return;
-       }
 
        /* Remove focus and firstFocus class */
        for (i = 0; i < webkit_dom_node_list_get_length (nodes); i++) {
@@ -631,6 +612,36 @@ theme_adium_remove_focus_marks (EmpathyThemeAdium *theme)
        }
 }
 
+static void
+theme_adium_remove_all_focus_marks (EmpathyThemeAdium *theme)
+{
+       EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
+       WebKitDOMDocument *dom;
+       WebKitDOMNodeList *nodes;
+       GError *error = NULL;
+
+       if (!priv->has_unread_message)
+               return;
+
+       priv->has_unread_message = FALSE;
+
+       dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (theme));
+       if (dom == NULL) {
+               return;
+       }
+
+       /* Get all nodes with focus class */
+       nodes = webkit_dom_document_query_selector_all (dom, ".focus", &error);
+       if (nodes == NULL) {
+               DEBUG ("Error getting focus nodes: %s",
+                       error ? error->message : "No error");
+               g_clear_error (&error);
+               return;
+       }
+
+       theme_adium_remove_focus_marks (theme, nodes);
+}
+
 static void
 theme_adium_append_message (EmpathyChatView *view,
                            EmpathyMessage  *msg)
@@ -638,6 +649,7 @@ theme_adium_append_message (EmpathyChatView *view,
        EmpathyThemeAdium     *theme = EMPATHY_THEME_ADIUM (view);
        EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
        EmpathyContact        *sender;
+       TpMessage             *tp_msg;
        TpAccount             *account;
        gchar                 *body_escaped;
        const gchar           *body;
@@ -759,6 +771,20 @@ theme_adium_append_message (EmpathyChatView *view,
         * %status% - See %status% in theme_adium_append_html ()
         */
 
+       /* This is slightly a hack, but it's the only way to add
+        * arbitrary data to messages in the HTML. We add another
+        * class called "x-empathy-message-id-*" to the message. This
+        * way, we can remove the unread marker for this specific
+        * message later. */
+       tp_msg = empathy_message_get_tp_message (msg);
+       if (tp_msg != NULL) {
+               gchar *tmp = tp_escape_as_identifier (
+                   tp_message_get_token (tp_msg));
+               g_string_append_printf (message_classes,
+                   " x-empathy-message-id-%s", tmp);
+               g_free (tmp);
+       }
+
        /* Define javascript function to use */
        if (consecutive) {
                func = priv->allow_scrolling ? "appendNextMessage" : "appendNextMessageNoScroll";
@@ -777,7 +803,7 @@ theme_adium_append_message (EmpathyChatView *view,
                }
 
                /* remove all the unread marks when we are sending a message */
-               theme_adium_remove_focus_marks (theme);
+               theme_adium_remove_all_focus_marks (theme);
        } else {
                /* in */
                if (is_backlog) {
@@ -926,17 +952,94 @@ theme_adium_copy_clipboard (EmpathyChatView *view)
        webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
 }
 
+static void
+theme_adium_remove_mark_from_message (EmpathyThemeAdium *self,
+                                     const gchar *token)
+{
+       WebKitDOMDocument *dom;
+       WebKitDOMNodeList *nodes;
+       gchar *class, *tmp;
+       GError *error = NULL;
+
+       dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (self));
+       if (dom == NULL) {
+               return;
+       }
+
+       tmp = tp_escape_as_identifier (token);
+       class = g_strdup_printf (".x-empathy-message-id-%s", tmp);
+       g_free (tmp);
+
+       /* Get all nodes with focus class */
+       nodes = webkit_dom_document_query_selector_all (dom, class, &error);
+       g_free (class);
+
+       if (nodes == NULL) {
+               DEBUG ("Error getting focus nodes: %s",
+                       error ? error->message : "No error");
+               g_clear_error (&error);
+               return;
+       }
+
+       theme_adium_remove_focus_marks (self, nodes);
+}
+
+static void
+theme_adium_remove_acked_message_unread_mark_foreach (gpointer data,
+                                                     gpointer user_data)
+{
+       EmpathyThemeAdium *self = user_data;
+       gchar *token = data;
+
+       theme_adium_remove_mark_from_message (self, token);
+       g_free (token);
+}
+
 static void
 theme_adium_focus_toggled (EmpathyChatView *view,
                           gboolean         has_focus)
 {
-       EmpathyThemeAdium *self = (EmpathyThemeAdium *) view;
        EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
 
        priv->has_focus = has_focus;
        if (!priv->has_focus) {
-               theme_adium_remove_focus_marks (self);
+               /* We've lost focus, so let's make sure all the acked
+                * messages have lost their unread marker. */
+               g_queue_foreach (&priv->acked_messages,
+                                theme_adium_remove_acked_message_unread_mark_foreach,
+                                view);
+               g_queue_clear (&priv->acked_messages);
+
+               priv->has_unread_message = FALSE;
+       }
+}
+
+static void
+theme_adium_message_acknowledged (EmpathyChatView *view,
+                                 EmpathyMessage  *message)
+{
+       EmpathyThemeAdium *self = (EmpathyThemeAdium *) view;
+       EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
+       TpMessage *tp_msg;
+
+       tp_msg = empathy_message_get_tp_message (message);
+
+       if (tp_msg == NULL) {
+               return;
        }
+
+       /* We only want to actually remove the unread marker if the
+        * view doesn't have focus. If we did it all the time we would
+        * never see the unread markers, ever! So, we'll queue these
+        * up, and when we lose focus, we'll remove the markers. */
+       if (priv->has_focus) {
+               g_queue_push_tail (&priv->acked_messages,
+                                  g_strdup (tp_message_get_token (tp_msg)));
+               return;
+       }
+
+       theme_adium_remove_mark_from_message (self,
+                                             tp_message_get_token (tp_msg));
 }
 
 static void
@@ -1059,6 +1162,7 @@ theme_adium_iface_init (EmpathyChatViewIface *iface)
        iface->highlight = theme_adium_highlight;
        iface->copy_clipboard = theme_adium_copy_clipboard;
        iface->focus_toggled = theme_adium_focus_toggled;
+       iface->message_acknowledged = theme_adium_message_acknowledged;
 }
 
 static void
@@ -1124,6 +1228,11 @@ theme_adium_dispose (GObject *object)
                priv->inspector_window = NULL;
        }
 
+       if (priv->acked_messages.length > 0) {
+               g_queue_foreach (&priv->acked_messages, (GFunc) g_free, NULL);
+               g_queue_clear (&priv->acked_messages);
+       }
+
        G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
 }
 
index 076a10053a0cfb0e1398a980a5cf0c1811fa9a4c..25ec498ce9e16589df610d139f5fa34a7f0c474f 100644 (file)
@@ -40,6 +40,7 @@
 
 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyMessage)
 typedef struct {
+       TpMessage *tp_message;
        TpChannelTextMessageType  type;
        EmpathyContact           *sender;
        EmpathyContact           *receiver;
@@ -73,6 +74,7 @@ enum {
        PROP_IS_BACKLOG,
        PROP_INCOMING,
        PROP_FLAGS,
+       PROP_TP_MESSAGE,
 };
 
 static void
@@ -153,6 +155,15 @@ empathy_message_class_init (EmpathyMessageClass *class)
                                                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
                                                               G_PARAM_CONSTRUCT_ONLY));
 
+       g_object_class_install_property (object_class,
+                                        PROP_TP_MESSAGE,
+                                        g_param_spec_object ("tp-message",
+                                                              "TpMessage",
+                                                              "The TpMessage of this message",
+                                                              TP_TYPE_MESSAGE,
+                                                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+                                                              G_PARAM_CONSTRUCT_ONLY));
+
        g_type_class_add_private (object_class, sizeof (EmpathyMessagePriv));
 
 }
@@ -181,6 +192,10 @@ empathy_message_finalize (GObject *object)
                g_object_unref (priv->receiver);
        }
 
+       if (priv->tp_message) {
+               g_object_unref (priv->tp_message);
+       }
+
        g_free (priv->body);
 
        G_OBJECT_CLASS (empathy_message_parent_class)->finalize (object);
@@ -221,6 +236,9 @@ message_get_property (GObject    *object,
        case PROP_FLAGS:
                g_value_set_uint (value, priv->flags);
                break;
+       case PROP_TP_MESSAGE:
+               g_value_set_object (value, priv->tp_message);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
                break;
@@ -267,6 +285,9 @@ message_set_property (GObject      *object,
        case PROP_FLAGS:
                priv->flags = g_value_get_uint (value);
                break;
+       case PROP_TP_MESSAGE:
+               priv->tp_message = g_value_dup_object (value);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
                break;
@@ -342,6 +363,18 @@ empathy_message_from_tpl_log_event (TplEvent *logevent)
        return retval;
 }
 
+TpMessage *
+empathy_message_get_tp_message (EmpathyMessage *message)
+{
+       EmpathyMessagePriv *priv;
+
+       g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), NULL);
+
+       priv = GET_PRIV (message);
+
+       return priv->tp_message;
+}
+
 TpChannelTextMessageType
 empathy_message_get_tptype (EmpathyMessage *message)
 {
@@ -563,16 +596,6 @@ empathy_message_type_to_str (TpChannelTextMessageType type)
        }
 }
 
-guint
-empathy_message_get_id (EmpathyMessage *message)
-{
-       EmpathyMessagePriv *priv = GET_PRIV (message);
-
-       g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), 0);
-
-       return priv->id;
-}
-
 gboolean
 empathy_message_is_incoming (EmpathyMessage *message)
 {
@@ -618,10 +641,8 @@ empathy_message_new_from_tp_message (TpMessage *tp_msg,
                                     gboolean incoming)
 {
        EmpathyMessage *message;
-       EmpathyMessagePriv *priv;
        gchar *body;
        TpChannelTextMessageFlags flags;
-       guint id;
 
        g_return_val_if_fail (TP_IS_MESSAGE (tp_msg), NULL);
 
@@ -634,17 +655,9 @@ empathy_message_new_from_tp_message (TpMessage *tp_msg,
                "flags", flags,
                "is-backlog", flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_SCROLLBACK,
                "incoming", incoming,
+               "tp-message", tp_msg,
                NULL);
 
-       priv = GET_PRIV (message);
-
-       /* FIXME: this is pretty low level, ideally we shouldn't have to use the
-        * ID directly but we don't use TpTextChannel's ack API everywhere yet. */
-       id = tp_asv_get_uint32 (tp_message_peek (tp_msg, 0),
-               "pending-message-id", NULL);
-
-       priv->id = id;
-
        g_free (body);
        return message;
 }
index 7508cb08e615f06f9c13e80c8c0c81996f948e5a..b20ceca16a2024b305c8d39c2e1a0176bab6ea6a 100644 (file)
@@ -59,6 +59,8 @@ EmpathyMessage *         empathy_message_from_tpl_log_event (TplEvent
 EmpathyMessage *         empathy_message_new_from_tp_message (TpMessage *tp_msg,
                                                              gboolean incoming);
 
+TpMessage *              empathy_message_get_tp_message    (EmpathyMessage *message);
+
 TpChannelTextMessageType empathy_message_get_tptype        (EmpathyMessage           *message);
 EmpathyContact *         empathy_message_get_sender        (EmpathyMessage           *message);
 void                     empathy_message_set_sender        (EmpathyMessage           *message,
@@ -75,8 +77,6 @@ gboolean                 empathy_message_should_highlight  (EmpathyMessage
 TpChannelTextMessageType empathy_message_type_from_str     (const gchar              *type_str);
 const gchar *            empathy_message_type_to_str       (TpChannelTextMessageType  type);
 
-guint                    empathy_message_get_id (EmpathyMessage *message);
-
 gboolean                 empathy_message_equal (EmpathyMessage *message1, EmpathyMessage *message2);
 
 TpChannelTextMessageFlags empathy_message_get_flags        (EmpathyMessage           *message);
index f33152d972cfae4c7daf5ab39b8b34bd981bc306..5144fa4d85de39f9e5eb43d38cf4d365b2e966a8 100644 (file)
@@ -81,6 +81,40 @@ empathy_time_to_string_local (gint64 t,
        return result;
 }
 
+gchar *
+empathy_duration_to_string (guint seconds)
+{
+       if (seconds < 60) {
+               return g_strdup_printf (ngettext ("%d second ago",
+                       "%d seconds ago", seconds), seconds);
+       }
+       else if (seconds < (60 * 60)) {
+               seconds /= 60;
+               return g_strdup_printf (ngettext ("%d minute ago",
+                       "%d minutes ago", seconds), seconds);
+       }
+       else if (seconds < (60 * 60 * 24)) {
+               seconds /= 60 * 60;
+               return g_strdup_printf (ngettext ("%d hour ago",
+                       "%d hours ago", seconds), seconds);
+       }
+       else if (seconds < (60 * 60 * 24 * 7)) {
+               seconds /= 60 * 60 * 24;
+               return g_strdup_printf (ngettext ("%d day ago",
+                       "%d days ago", seconds), seconds);
+       }
+       else if (seconds < (60 * 60 * 24 * 30)) {
+               seconds /= 60 * 60 * 24 * 7;
+               return g_strdup_printf (ngettext ("%d week ago",
+                       "%d weeks ago", seconds), seconds);
+       }
+       else {
+               seconds /= 60 * 60 * 24 * 30;
+               return g_strdup_printf (ngettext ("%d month ago",
+                       "%d months ago", seconds), seconds);
+       }
+}
+
 gchar  *
 empathy_time_to_string_relative (gint64 t)
 {
@@ -96,35 +130,7 @@ empathy_time_to_string_relative (gint64 t)
        seconds = delta / G_TIME_SPAN_SECOND;
 
        if (seconds > 0) {
-               if (seconds < 60) {
-                       result = g_strdup_printf (ngettext ("%d second ago",
-                               "%d seconds ago", seconds), seconds);
-               }
-               else if (seconds < (60 * 60)) {
-                       seconds /= 60;
-                       result = g_strdup_printf (ngettext ("%d minute ago",
-                               "%d minutes ago", seconds), seconds);
-               }
-               else if (seconds < (60 * 60 * 24)) {
-                       seconds /= 60 * 60;
-                       result = g_strdup_printf (ngettext ("%d hour ago",
-                               "%d hours ago", seconds), seconds);
-               }
-               else if (seconds < (60 * 60 * 24 * 7)) {
-                       seconds /= 60 * 60 * 24;
-                       result = g_strdup_printf (ngettext ("%d day ago",
-                               "%d days ago", seconds), seconds);
-               }
-               else if (seconds < (60 * 60 * 24 * 30)) {
-                       seconds /= 60 * 60 * 24 * 7;
-                       result = g_strdup_printf (ngettext ("%d week ago",
-                               "%d weeks ago", seconds), seconds);
-               }
-               else {
-                       seconds /= 60 * 60 * 24 * 30;
-                       result = g_strdup_printf (ngettext ("%d month ago",
-                               "%d months ago", seconds), seconds);
-               }
+               result = empathy_duration_to_string (seconds);
        }
        else {
                result = g_strdup (_("in the future"));
index 7fac48221ae91ecbfcaf4e961c64349e92fcb56a..3a22adeee20bd98ac8a0351bbf2b80918f67bd5f 100644 (file)
@@ -45,6 +45,7 @@ gchar  *empathy_time_to_string_utc   (gint64       t,
 gchar  *empathy_time_to_string_local (gint64       t,
                                      const gchar *format);
 gchar  *empathy_time_to_string_relative (gint64 t);
+gchar *empathy_duration_to_string (guint seconds);
 
 G_END_DECLS
 
index 8e6672eee8cd0355a895f6f583fd38b684a6addf..0541943b96ef76828b4dfcf439e76d5d3f5cc9c7 100644 (file)
@@ -84,6 +84,7 @@ enum {
        CHAT_STATE_CHANGED,
        PROPERTY_CHANGED,
        DESTROY,
+       MESSAGE_ACKNOWLEDGED,
        LAST_SIGNAL
 };
 
@@ -93,8 +94,6 @@ G_DEFINE_TYPE_WITH_CODE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST,
                                                tp_chat_iface_init));
 
-static void acknowledge_messages (EmpathyTpChat *chat, GArray *ids);
-
 static void
 tp_chat_set_delivery_status (EmpathyTpChat         *self,
                             const gchar           *token,
@@ -453,6 +452,39 @@ message_received_cb (TpTextChannel   *channel,
        handle_incoming_message (chat, message, FALSE);
 }
 
+static gboolean
+find_pending_message_func (gconstpointer a,
+                          gconstpointer b)
+{
+       EmpathyMessage *msg = (EmpathyMessage *) a;
+       TpMessage *message = (TpMessage *) b;
+
+       if (empathy_message_get_tp_message (msg) == message)
+               return 0;
+
+       return -1;
+}
+
+static void
+pending_message_removed_cb (TpTextChannel   *channel,
+                           TpMessage *message,
+                           EmpathyTpChat *chat)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (chat);
+       GList *m;
+
+       m = g_queue_find_custom (priv->pending_messages_queue, message,
+                                find_pending_message_func);
+
+       if (m == NULL)
+               return;
+
+       g_signal_emit (chat, signals[MESSAGE_ACKNOWLEDGED], 0, m->data);
+
+       g_object_unref (m->data);
+       g_queue_delete_link (priv->pending_messages_queue, m);
+}
+
 static void
 message_sent_cb (TpTextChannel   *channel,
                 TpMessage *message,
@@ -911,6 +943,8 @@ check_almost_ready (EmpathyTpChat *chat)
 
        tp_g_signal_connect_object (priv->channel, "message-received",
                G_CALLBACK (message_received_cb), chat, 0);
+       tp_g_signal_connect_object (priv->channel, "pending-message-removed",
+               G_CALLBACK (pending_message_removed_cb), chat, 0);
 
        list_pending_messages (chat);
 
@@ -1632,6 +1666,16 @@ empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
                              G_TYPE_NONE,
                              0);
 
+       signals[MESSAGE_ACKNOWLEDGED] =
+               g_signal_new ("message-acknowledged",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__OBJECT,
+                             G_TYPE_NONE,
+                             1, EMPATHY_TYPE_MESSAGE);
+
        g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
 }
 
@@ -1793,84 +1837,51 @@ empathy_tp_chat_get_pending_messages (EmpathyTpChat *chat)
        return priv->pending_messages_queue->head;
 }
 
-static void
-acknowledge_messages (EmpathyTpChat *chat, GArray *ids) {
-       EmpathyTpChatPriv *priv = GET_PRIV (chat);
-
-       tp_cli_channel_type_text_call_acknowledge_pending_messages (
-               priv->channel, -1, ids, tp_chat_async_cb,
-               "acknowledging received message", NULL, G_OBJECT (chat));
-}
-
 void
 empathy_tp_chat_acknowledge_message (EmpathyTpChat *chat,
                                     EmpathyMessage *message) {
        EmpathyTpChatPriv *priv = GET_PRIV (chat);
-       GArray *message_ids;
-       GList *m;
-       guint id;
+       TpMessage *tp_msg;
 
        g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
        g_return_if_fail (priv->ready);
 
        if (!empathy_message_is_incoming (message))
-               goto out;
-
-       message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
-
-       id = empathy_message_get_id (message);
-       g_array_append_val (message_ids, id);
-       acknowledge_messages (chat, message_ids);
-       g_array_free (message_ids, TRUE);
+               return;
 
-out:
-       m = g_queue_find (priv->pending_messages_queue, message);
-       g_assert (m != NULL);
-       g_queue_delete_link (priv->pending_messages_queue, m);
-       g_object_unref (message);
+       tp_msg = empathy_message_get_tp_message (message);
+       tp_text_channel_ack_message_async (TP_TEXT_CHANNEL (priv->channel),
+                                          tp_msg, NULL, NULL);
 }
 
 void
 empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
                                      const GSList *messages) {
        EmpathyTpChatPriv *priv = GET_PRIV (chat);
-       /* Copy messages as the messges list (probably is) our own */
-       GSList *msgs = g_slist_copy ((GSList *) messages);
-       GSList *l;
-       guint length;
-       GArray *message_ids;
+       const GSList *l;
+       GList *messages_to_ack = NULL;
 
        g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
        g_return_if_fail (priv->ready);
 
-       length = g_slist_length ((GSList *) messages);
-
-       if (length == 0)
+       if (messages == NULL)
                return;
 
-       message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), length);
-
-       for (l = msgs; l != NULL; l = g_slist_next (l)) {
-               GList *m;
-
+       for (l = messages; l != NULL; l = g_slist_next (l)) {
                EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
 
-               m = g_queue_find (priv->pending_messages_queue, message);
-               g_assert (m != NULL);
-               g_queue_delete_link (priv->pending_messages_queue, m);
-
                if (empathy_message_is_incoming (message)) {
-                       guint id = empathy_message_get_id (message);
-                       g_array_append_val (message_ids, id);
+                       TpMessage *tp_msg = empathy_message_get_tp_message (message);
+                       messages_to_ack = g_list_append (messages_to_ack, tp_msg);
                }
-               g_object_unref (message);
        }
 
-       if (message_ids->len > 0)
-               acknowledge_messages (chat, message_ids);
+       if (messages_to_ack != NULL) {
+               tp_text_channel_ack_messages_async (TP_TEXT_CHANNEL (priv->channel),
+                                                   messages_to_ack, NULL, NULL);
+       }
 
-       g_array_free (message_ids, TRUE);
-       g_slist_free (msgs);
+       g_list_free (messages_to_ack);
 }
 
 void
index 3562a04f073e512ffedef3c237b610aed9efe2b6..411d76c71ed250009bdc0cc9935abc08e8a632e0 100644 (file)
--- a/po/es.po
+++ b/po/es.po
@@ -1,23 +1,23 @@
 # translation of empathy.master.po to Español
 # Copyright (C) 2003 Free Software Foundation
 # This file is distributed under the same license as the Gossip package.
-# Daniel Mustieles <daniel.mustieles@gmail.com>, 2010, 2011.
 # Jorge González <jorgegonz@svn.gnome.org>, 2007, 2008, 2009, 2010, 2011.
+# Daniel Mustieles <daniel.mustieles@gmail.com>, 2010, 2011.
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: empathy.master\n"
 "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?"
 "product=empathy&keywords=I18N+L10N&component=General\n"
-"POT-Creation-Date: 2011-05-09 10:59+0000\n"
-"PO-Revision-Date: 2011-05-09 14:23+0200\n"
-"Last-Translator: Jorge González <jorgegonz@svn.gnome.org>\n"
+"POT-Creation-Date: 2011-05-16 13:17+0000\n"
+"PO-Revision-Date: 2011-05-16 17:07+0200\n"
+"Last-Translator: Daniel Mustieles <daniel.mustieles@gmail.com>\n"
 "Language-Team: Español <gnome-es-list@gnome.org>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "X-Generator: KBabel 1.11.4\n"
-"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
 #: ../data/empathy.desktop.in.in.h:1
 msgid "Chat on Google Talk, Facebook, MSN and many other chat services"
@@ -700,49 +700,49 @@ msgstr "Google Talk"
 msgid "Facebook Chat"
 msgstr "Chat de Facebook"
 
-#: ../libempathy/empathy-time.c:100
+#: ../libempathy/empathy-time.c:88
 #, c-format
 msgid "%d second ago"
 msgid_plural "%d seconds ago"
 msgstr[0] "hace %d segundo"
 msgstr[1] "hace %d segundos"
 
-#: ../libempathy/empathy-time.c:105
+#: ../libempathy/empathy-time.c:93
 #, c-format
 msgid "%d minute ago"
 msgid_plural "%d minutes ago"
 msgstr[0] "hace %d minuto"
 msgstr[1] "hace %d minutos"
 
-#: ../libempathy/empathy-time.c:110
+#: ../libempathy/empathy-time.c:98
 #, c-format
 msgid "%d hour ago"
 msgid_plural "%d hours ago"
 msgstr[0] "hace %d hora"
 msgstr[1] "hace %d horas"
 
-#: ../libempathy/empathy-time.c:115
+#: ../libempathy/empathy-time.c:103
 #, c-format
 msgid "%d day ago"
 msgid_plural "%d days ago"
 msgstr[0] "hace %d día"
 msgstr[1] "hace %d días"
 
-#: ../libempathy/empathy-time.c:120
+#: ../libempathy/empathy-time.c:108
 #, c-format
 msgid "%d week ago"
 msgid_plural "%d weeks ago"
 msgstr[0] "hace %d semana"
 msgstr[1] "hace %d semanas"
 
-#: ../libempathy/empathy-time.c:125
+#: ../libempathy/empathy-time.c:113
 #, c-format
 msgid "%d month ago"
 msgid_plural "%d months ago"
 msgstr[0] "hace %d mes"
 msgstr[1] "hace %d meses"
 
-#: ../libempathy/empathy-time.c:130
+#: ../libempathy/empathy-time.c:136
 msgid "in the future"
 msgstr "en el futuro"
 
@@ -1191,6 +1191,7 @@ msgid "STUN Server:"
 msgstr "Servidor STUN:"
 
 #: ../libempathy-gtk/empathy-account-widget-sip.ui.h:18
+#: ../libempathy-gtk/empathy-contact-widget.c:347
 msgid "Server:"
 msgstr "Servidor:"
 
@@ -1259,35 +1260,41 @@ msgstr "Todos los archivos"
 msgid "Click to enlarge"
 msgstr "Pulse para agrandar"
 
-#: ../libempathy-gtk/empathy-chat.c:668
+#: ../libempathy-gtk/empathy-chat.c:669
 msgid "Failed to open private chat"
 msgstr "Falló al abrir el chat privado"
 
-#: ../libempathy-gtk/empathy-chat.c:733
+#: ../libempathy-gtk/empathy-chat.c:732
 msgid "Topic not supported on this conversation"
 msgstr "El tema no está soportado en esta conversación"
 
-#: ../libempathy-gtk/empathy-chat.c:739
+#: ../libempathy-gtk/empathy-chat.c:738
 msgid "You are not allowed to change the topic"
 msgstr "No le está permitido cambiar el tema"
 
-#: ../libempathy-gtk/empathy-chat.c:948
+#: ../libempathy-gtk/empathy-chat.c:945
+#, c-format
+#| msgid "invalid contact"
+msgid "“%s” is not a valid contact ID"
+msgstr "%s no es un identificador de contacto válido"
+
+#: ../libempathy-gtk/empathy-chat.c:1002
 msgid "/clear: clear all messages from the current conversation"
 msgstr "/clear: limpiar todos los mensajes de la conversación actual"
 
-#: ../libempathy-gtk/empathy-chat.c:951
+#: ../libempathy-gtk/empathy-chat.c:1005
 msgid "/topic <topic>: set the topic of the current conversation"
 msgstr "/topic <tema>: establecer el tema para la conversación actual"
 
-#: ../libempathy-gtk/empathy-chat.c:954
+#: ../libempathy-gtk/empathy-chat.c:1008
 msgid "/join <chat room ID>: join a new chat room"
 msgstr "/join <id de sala de chat>: unirse a una sala de chat nueva"
 
-#: ../libempathy-gtk/empathy-chat.c:957
+#: ../libempathy-gtk/empathy-chat.c:1011
 msgid "/j <chat room ID>: join a new chat room"
 msgstr "/j <id de sala de chat>: unirse a una sala de chat nueva"
 
-#: ../libempathy-gtk/empathy-chat.c:962
+#: ../libempathy-gtk/empathy-chat.c:1015
 msgid ""
 "/part [<chat room ID>] [<reason>]: leave the chat room, by default the "
 "current one"
@@ -1295,23 +1302,23 @@ msgstr ""
 "/part [<ID de la sala de chat>] [<razón>]: abandonar la sala de chat, la "
 "actual de manera predeterminada"
 
-#: ../libempathy-gtk/empathy-chat.c:967
+#: ../libempathy-gtk/empathy-chat.c:1019
 msgid "/query <contact ID> [<message>]: open a private chat"
 msgstr "/query <id del contacto> [<mensaje>]: abrir un chat privado"
 
-#: ../libempathy-gtk/empathy-chat.c:970
+#: ../libempathy-gtk/empathy-chat.c:1022
 msgid "/msg <contact ID> <message>: open a private chat"
 msgstr "/msg <id del contacto> <mensaje>: abrir un chat privado"
 
-#: ../libempathy-gtk/empathy-chat.c:973
+#: ../libempathy-gtk/empathy-chat.c:1025
 msgid "/nick <nickname>: change your nickname on the current server"
 msgstr "/nick <apodo>: cambiar su apodo en el servidor actual"
 
-#: ../libempathy-gtk/empathy-chat.c:976
+#: ../libempathy-gtk/empathy-chat.c:1028
 msgid "/me <message>: send an ACTION message to the current conversation"
 msgstr "/me <mensaje>: enviar un mensaje de ACCIÓN a la conversación actual"
 
-#: ../libempathy-gtk/empathy-chat.c:979
+#: ../libempathy-gtk/empathy-chat.c:1031
 msgid ""
 "/say <message>: send <message> to the current conversation. This is used to "
 "send a message starting with a '/'. For example: \"/say /join is used to "
@@ -1321,7 +1328,11 @@ msgstr ""
 "para enviar un mensaje comenzando por una «/». Por ejemplo: «/say /join se usa "
 "para unirse a una sala de chat nueva»"
 
-#: ../libempathy-gtk/empathy-chat.c:984
+#: ../libempathy-gtk/empathy-chat.c:1036
+msgid "/whois <contact ID>: display information about a contact"
+msgstr "/whois <ID del contacto>: mostrar información sobre un contacto"
+
+#: ../libempathy-gtk/empathy-chat.c:1039
 msgid ""
 "/help [<command>]: show all supported commands. If <command> is defined, "
 "show its usage."
@@ -1329,113 +1340,113 @@ msgstr ""
 "/help [<comando>]: mostrar todos los comandos soportados. Si <comando> está "
 "definido, muestra su uso."
 
-#: ../libempathy-gtk/empathy-chat.c:994
+#: ../libempathy-gtk/empathy-chat.c:1049
 #, c-format
 msgid "Usage: %s"
 msgstr "Uso: %s"
 
-#: ../libempathy-gtk/empathy-chat.c:1033
+#: ../libempathy-gtk/empathy-chat.c:1088
 msgid "Unknown command"
 msgstr "Comando desconocido"
 
-#: ../libempathy-gtk/empathy-chat.c:1159
+#: ../libempathy-gtk/empathy-chat.c:1214
 msgid "Unknown command; see /help for the available commands"
 msgstr "Comando desconocido; consulte /help para ver los comandos disponibles"
 
 #. translators: error used when user doesn't have enough credit on his
 #. * account to send the message.
-#: ../libempathy-gtk/empathy-chat.c:1299
+#: ../libempathy-gtk/empathy-chat.c:1354
 msgid "insufficient balance to send message"
 msgstr "no tiene balance suficiente para enviar el mensaje"
 
-#: ../libempathy-gtk/empathy-chat.c:1301
+#: ../libempathy-gtk/empathy-chat.c:1356
 msgid "not capable"
 msgstr "no es posible"
 
-#: ../libempathy-gtk/empathy-chat.c:1308
+#: ../libempathy-gtk/empathy-chat.c:1363
 msgid "offline"
 msgstr "desconectado"
 
-#: ../libempathy-gtk/empathy-chat.c:1311
+#: ../libempathy-gtk/empathy-chat.c:1366
 msgid "invalid contact"
 msgstr "contacto no válido"
 
-#: ../libempathy-gtk/empathy-chat.c:1314
+#: ../libempathy-gtk/empathy-chat.c:1369
 msgid "permission denied"
 msgstr "permiso denegado"
 
-#: ../libempathy-gtk/empathy-chat.c:1317
+#: ../libempathy-gtk/empathy-chat.c:1372
 msgid "too long message"
 msgstr "mensaje demasiado largo"
 
-#: ../libempathy-gtk/empathy-chat.c:1320
+#: ../libempathy-gtk/empathy-chat.c:1375
 msgid "not implemented"
 msgstr "no implementado"
 
-#: ../libempathy-gtk/empathy-chat.c:1324
+#: ../libempathy-gtk/empathy-chat.c:1379
 msgid "unknown"
 msgstr "desconocido"
 
-#: ../libempathy-gtk/empathy-chat.c:1330
+#: ../libempathy-gtk/empathy-chat.c:1385
 #, c-format
 msgid "Error sending message '%s': %s"
 msgstr "Error al enviar el mensaje «%s»: %s"
 
-#: ../libempathy-gtk/empathy-chat.c:1334
+#: ../libempathy-gtk/empathy-chat.c:1389
 #, c-format
 msgid "Error sending message: %s"
 msgstr "Error al enviar el mensaje: %s"
 
-#: ../libempathy-gtk/empathy-chat.c:1395 ../src/empathy-chat-window.c:760
+#: ../libempathy-gtk/empathy-chat.c:1450 ../src/empathy-chat-window.c:760
 msgid "Topic:"
 msgstr "Tema:"
 
-#: ../libempathy-gtk/empathy-chat.c:1407
+#: ../libempathy-gtk/empathy-chat.c:1462
 #, c-format
 msgid "Topic set to: %s"
 msgstr "El tema se ha establecido a: %s"
 
-#: ../libempathy-gtk/empathy-chat.c:1409
+#: ../libempathy-gtk/empathy-chat.c:1464
 msgid "No topic defined"
 msgstr "No se ha definido el tema"
 
-#: ../libempathy-gtk/empathy-chat.c:1916
+#: ../libempathy-gtk/empathy-chat.c:1971
 msgid "(No Suggestions)"
 msgstr "(Sin sugerencias)"
 
 #. translators: %s is the selected word
-#: ../libempathy-gtk/empathy-chat.c:1984
+#: ../libempathy-gtk/empathy-chat.c:2039
 #, c-format
 msgid "Add '%s' to Dictionary"
 msgstr "Añadir «%s» al diccionario"
 
 #. translators: first %s is the selected word,
 #. * second %s is the language name of the target dictionary
-#: ../libempathy-gtk/empathy-chat.c:2021
+#: ../libempathy-gtk/empathy-chat.c:2076
 #, c-format
 msgid "Add '%s' to %s Dictionary"
 msgstr "Añadir «%s» al diccionario de «%s»"
 
-#: ../libempathy-gtk/empathy-chat.c:2078
+#: ../libempathy-gtk/empathy-chat.c:2133
 msgid "Insert Smiley"
 msgstr "Insertar emoticono"
 
 #. send button
-#: ../libempathy-gtk/empathy-chat.c:2096
+#: ../libempathy-gtk/empathy-chat.c:2151
 #: ../libempathy-gtk/empathy-ui-utils.c:1808
 msgid "_Send"
 msgstr "E_nviar"
 
 #. Spelling suggestions
-#: ../libempathy-gtk/empathy-chat.c:2131
+#: ../libempathy-gtk/empathy-chat.c:2186
 msgid "_Spelling Suggestions"
 msgstr "_Sugerencias ortográficas"
 
-#: ../libempathy-gtk/empathy-chat.c:2220
+#: ../libempathy-gtk/empathy-chat.c:2275
 msgid "Failed to retrieve recent logs"
 msgstr "Falló al recibir los registros recientes"
 
-#: ../libempathy-gtk/empathy-chat.c:2331
+#: ../libempathy-gtk/empathy-chat.c:2386
 #, c-format
 msgid "%s has disconnected"
 msgstr "%s se ha desconectado"
@@ -1443,12 +1454,12 @@ msgstr "%s se ha desconectado"
 #. translators: reverse the order of these arguments
 #. * if the kicked should come before the kicker in your locale.
 #.
-#: ../libempathy-gtk/empathy-chat.c:2338
+#: ../libempathy-gtk/empathy-chat.c:2393
 #, c-format
 msgid "%1$s was kicked by %2$s"
 msgstr "%2$s expulsó a %1$s"
 
-#: ../libempathy-gtk/empathy-chat.c:2341
+#: ../libempathy-gtk/empathy-chat.c:2396
 #, c-format
 msgid "%s was kicked"
 msgstr "%s fue expulsado"
@@ -1456,17 +1467,17 @@ msgstr "%s fue expulsado"
 #. translators: reverse the order of these arguments
 #. * if the banned should come before the banner in your locale.
 #.
-#: ../libempathy-gtk/empathy-chat.c:2349
+#: ../libempathy-gtk/empathy-chat.c:2404
 #, c-format
 msgid "%1$s was banned by %2$s"
 msgstr "%2$s vetó a %1$s"
 
-#: ../libempathy-gtk/empathy-chat.c:2352
+#: ../libempathy-gtk/empathy-chat.c:2407
 #, c-format
 msgid "%s was banned"
 msgstr "%s fue vetado"
 
-#: ../libempathy-gtk/empathy-chat.c:2356
+#: ../libempathy-gtk/empathy-chat.c:2411
 #, c-format
 msgid "%s has left the room"
 msgstr "%s ha dejado la sala"
@@ -1476,69 +1487,69 @@ msgstr "%s ha dejado la sala"
 #. * given by the user living the room. If this poses a problem,
 #. * please let us know. :-)
 #.
-#: ../libempathy-gtk/empathy-chat.c:2365
+#: ../libempathy-gtk/empathy-chat.c:2420
 #, c-format
 msgid " (%s)"
 msgstr " (%s)"
 
-#: ../libempathy-gtk/empathy-chat.c:2390
+#: ../libempathy-gtk/empathy-chat.c:2445
 #, c-format
 msgid "%s has joined the room"
 msgstr "%s ha entrado en la sala"
 
-#: ../libempathy-gtk/empathy-chat.c:2415
+#: ../libempathy-gtk/empathy-chat.c:2470
 #, c-format
 msgid "%s is now known as %s"
 msgstr "Ahora %s se llama %s"
 
-#: ../libempathy-gtk/empathy-chat.c:2554
+#: ../libempathy-gtk/empathy-chat.c:2609
 #: ../src/empathy-streamed-media-window.c:1957
-#: ../src/empathy-event-manager.c:1116
+#: ../src/empathy-event-manager.c:1117
 msgid "Disconnected"
 msgstr "Desconectado"
 
 #. Add message
-#: ../libempathy-gtk/empathy-chat.c:3202
+#: ../libempathy-gtk/empathy-chat.c:3267
 msgid "Would you like to store this password?"
 msgstr "¿Quiere guardar esta contraseña?"
 
-#: ../libempathy-gtk/empathy-chat.c:3208
+#: ../libempathy-gtk/empathy-chat.c:3273
 msgid "Remember"
 msgstr "Recordar"
 
-#: ../libempathy-gtk/empathy-chat.c:3218
+#: ../libempathy-gtk/empathy-chat.c:3283
 msgid "Not now"
 msgstr "Ahora no"
 
-#: ../libempathy-gtk/empathy-chat.c:3262
+#: ../libempathy-gtk/empathy-chat.c:3327
 msgid "Retry"
 msgstr "Volver a intentarlo"
 
-#: ../libempathy-gtk/empathy-chat.c:3266
+#: ../libempathy-gtk/empathy-chat.c:3331
 msgid "Wrong password; please try again:"
 msgstr "Contraseña incorrecta; inténtelo de nuevo:"
 
 #. Add message
-#: ../libempathy-gtk/empathy-chat.c:3383
+#: ../libempathy-gtk/empathy-chat.c:3460
 msgid "This room is protected by a password:"
 msgstr "Esta sala está protegida por contraseña:"
 
-#: ../libempathy-gtk/empathy-chat.c:3410
+#: ../libempathy-gtk/empathy-chat.c:3487
 msgid "Join"
 msgstr "Unirse"
 
-#: ../libempathy-gtk/empathy-chat.c:3602 ../src/empathy-event-manager.c:1137
+#: ../libempathy-gtk/empathy-chat.c:3686 ../src/empathy-event-manager.c:1138
 msgid "Connected"
 msgstr "Conectado"
 
-#: ../libempathy-gtk/empathy-chat.c:3657
+#: ../libempathy-gtk/empathy-chat.c:3741
 #: ../libempathy-gtk/empathy-log-window.c:650
 msgid "Conversation"
 msgstr "Conversación"
 
 #. Translators: this string is a something like
 #. * "Escher Cat (SMS)"
-#: ../libempathy-gtk/empathy-chat.c:3662
+#: ../libempathy-gtk/empathy-chat.c:3746
 #, c-format
 msgid "%s (SMS)"
 msgstr "%s (SMS)"
@@ -1580,13 +1591,13 @@ msgstr "Cuenta:"
 
 #. Copy Link Address menu item
 #: ../libempathy-gtk/empathy-chat-text-view.c:320
-#: ../libempathy-gtk/empathy-theme-adium.c:996
+#: ../libempathy-gtk/empathy-theme-adium.c:1002
 msgid "_Copy Link Address"
 msgstr "_Copiar la dirección del enlace"
 
 #. Open Link menu item
 #: ../libempathy-gtk/empathy-chat-text-view.c:327
-#: ../libempathy-gtk/empathy-theme-adium.c:1003
+#: ../libempathy-gtk/empathy-theme-adium.c:1009
 msgid "_Open Link"
 msgstr "_Abrir enlace"
 
@@ -1701,58 +1712,58 @@ msgstr "_Bloquear contacto"
 msgid "_Chat"
 msgstr "_Chat"
 
-#: ../libempathy-gtk/empathy-contact-menu.c:359
+#: ../libempathy-gtk/empathy-contact-menu.c:358
 #: ../libempathy-gtk/empathy-individual-menu.c:625
 msgctxt "menu item"
 msgid "_Audio Call"
 msgstr "Llamada de vo_z"
 
-#: ../libempathy-gtk/empathy-contact-menu.c:390
+#: ../libempathy-gtk/empathy-contact-menu.c:389
 #: ../libempathy-gtk/empathy-individual-menu.c:667
 msgctxt "menu item"
 msgid "_Video Call"
 msgstr "Llamada de _vídeo"
 
-#: ../libempathy-gtk/empathy-contact-menu.c:436
+#: ../libempathy-gtk/empathy-contact-menu.c:435
 #: ../libempathy-gtk/empathy-individual-menu.c:710
 #: ../src/empathy-main-window.ui.h:27
 msgid "_Previous Conversations"
 msgstr "Conversaciones an_teriores"
 
-#: ../libempathy-gtk/empathy-contact-menu.c:458
+#: ../libempathy-gtk/empathy-contact-menu.c:457
 #: ../libempathy-gtk/empathy-individual-menu.c:751
 msgid "Send File"
 msgstr "Enviar archivo"
 
-#: ../libempathy-gtk/empathy-contact-menu.c:481
+#: ../libempathy-gtk/empathy-contact-menu.c:480
 #: ../libempathy-gtk/empathy-individual-menu.c:793
 msgid "Share My Desktop"
 msgstr "Compartir mi escritorio"
 
-#: ../libempathy-gtk/empathy-contact-menu.c:521
-#: ../libempathy-gtk/empathy-contact-widget.c:1751
+#: ../libempathy-gtk/empathy-contact-menu.c:520
+#: ../libempathy-gtk/empathy-contact-widget.c:1890
 #: ../libempathy-gtk/empathy-individual-menu.c:828
 #: ../libempathy-gtk/empathy-individual-widget.c:1370
 msgid "Favorite"
 msgstr "Favorita"
 
-#: ../libempathy-gtk/empathy-contact-menu.c:550
+#: ../libempathy-gtk/empathy-contact-menu.c:549
 #: ../libempathy-gtk/empathy-individual-menu.c:856
 msgid "Infor_mation"
 msgstr "Infor_mación"
 
-#: ../libempathy-gtk/empathy-contact-menu.c:596
+#: ../libempathy-gtk/empathy-contact-menu.c:595
 msgctxt "Edit contact (contextual menu)"
 msgid "_Edit"
 msgstr "_Editar"
 
-#: ../libempathy-gtk/empathy-contact-menu.c:650
+#: ../libempathy-gtk/empathy-contact-menu.c:649
 #: ../libempathy-gtk/empathy-individual-menu.c:1037
 #: ../src/empathy-chat-window.c:986
 msgid "Inviting you to this room"
 msgstr "Invitándolo a esta sala"
 
-#: ../libempathy-gtk/empathy-contact-menu.c:681
+#: ../libempathy-gtk/empathy-contact-menu.c:680
 #: ../libempathy-gtk/empathy-individual-menu.c:1084
 msgid "_Invite to Chat Room"
 msgstr "_Invitar a sala de chat"
@@ -1778,153 +1789,180 @@ msgstr "No se encontraron contactos"
 msgid "Select a contact"
 msgstr "Seleccionar un contacto"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:295
+#: ../libempathy-gtk/empathy-contact-widget.c:336
 #: ../libempathy-gtk/empathy-individual-widget.c:153
 msgid "Full name:"
 msgstr "Nombre completo:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:296
+#: ../libempathy-gtk/empathy-contact-widget.c:337
 #: ../libempathy-gtk/empathy-individual-widget.c:154
 msgid "Phone number:"
 msgstr "Número de teléfono:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:297
+#: ../libempathy-gtk/empathy-contact-widget.c:338
 #: ../libempathy-gtk/empathy-individual-widget.c:155
 msgid "E-mail address:"
 msgstr "Dirección de correo-e:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:298
+#: ../libempathy-gtk/empathy-contact-widget.c:339
 #: ../libempathy-gtk/empathy-individual-widget.c:156
 msgid "Website:"
 msgstr "Página web:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:299
+#: ../libempathy-gtk/empathy-contact-widget.c:340
 #: ../libempathy-gtk/empathy-individual-widget.c:157
 msgid "Birthday:"
 msgstr "Cumpleaños:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:762
+#. Note to translators: this is the caption for a string of the form "5
+#. * minutes ago", and refers to the time since the contact last interacted
+#. * with their IM client.
+#.
+#: ../libempathy-gtk/empathy-contact-widget.c:346
+#| msgid "_Last Name:"
+msgid "Last seen:"
+msgstr "Última actividad:"
+
+#: ../libempathy-gtk/empathy-contact-widget.c:348
+#| msgid "Connected"
+msgid "Connected from:"
+msgstr "Conectado desde:"
+
+#. FIXME: once Idle implements SimplePresence using this information, we can
+#. * and should bin this.
+#.
+#: ../libempathy-gtk/empathy-contact-widget.c:353
+#| msgid "Quit message:"
+msgid "Away message:"
+msgstr "Mensaje de ausencia:"
+
+#: ../libempathy-gtk/empathy-contact-widget.c:606
+#| msgid "Ca_ncel"
+msgid "Channels:"
+msgstr "Canales:"
+
+#: ../libempathy-gtk/empathy-contact-widget.c:901
 #: ../libempathy-gtk/empathy-individual-widget.c:488
 msgid "Country ISO Code:"
 msgstr "Código ISO de país:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:764
+#: ../libempathy-gtk/empathy-contact-widget.c:903
 #: ../libempathy-gtk/empathy-individual-widget.c:490
 msgid "Country:"
 msgstr "País:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:766
+#: ../libempathy-gtk/empathy-contact-widget.c:905
 #: ../libempathy-gtk/empathy-individual-widget.c:492
 msgid "State:"
 msgstr "Estado:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:768
+#: ../libempathy-gtk/empathy-contact-widget.c:907
 #: ../libempathy-gtk/empathy-individual-widget.c:494
 msgid "City:"
 msgstr "Ciudad:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:770
+#: ../libempathy-gtk/empathy-contact-widget.c:909
 #: ../libempathy-gtk/empathy-individual-widget.c:496
 msgid "Area:"
 msgstr "Área:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:772
+#: ../libempathy-gtk/empathy-contact-widget.c:911
 #: ../libempathy-gtk/empathy-individual-widget.c:498
 msgid "Postal Code:"
 msgstr "Código postal:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:774
+#: ../libempathy-gtk/empathy-contact-widget.c:913
 #: ../libempathy-gtk/empathy-individual-widget.c:500
 msgid "Street:"
 msgstr "Calle:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:776
+#: ../libempathy-gtk/empathy-contact-widget.c:915
 #: ../libempathy-gtk/empathy-individual-widget.c:502
 msgid "Building:"
 msgstr "Edificio:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:778
+#: ../libempathy-gtk/empathy-contact-widget.c:917
 #: ../libempathy-gtk/empathy-individual-widget.c:504
 msgid "Floor:"
 msgstr "Planta:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:780
+#: ../libempathy-gtk/empathy-contact-widget.c:919
 #: ../libempathy-gtk/empathy-individual-widget.c:506
 msgid "Room:"
 msgstr "Habitación:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:782
+#: ../libempathy-gtk/empathy-contact-widget.c:921
 #: ../libempathy-gtk/empathy-individual-widget.c:508
 msgid "Text:"
 msgstr "Texto:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:784
+#: ../libempathy-gtk/empathy-contact-widget.c:923
 #: ../libempathy-gtk/empathy-individual-widget.c:510
 msgid "Description:"
 msgstr "Descripción:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:786
+#: ../libempathy-gtk/empathy-contact-widget.c:925
 #: ../libempathy-gtk/empathy-individual-widget.c:512
 msgid "URI:"
 msgstr "URI:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:788
+#: ../libempathy-gtk/empathy-contact-widget.c:927
 #: ../libempathy-gtk/empathy-individual-widget.c:514
 msgid "Accuracy Level:"
 msgstr "Nivel de precisión:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:790
+#: ../libempathy-gtk/empathy-contact-widget.c:929
 #: ../libempathy-gtk/empathy-individual-widget.c:516
 msgid "Error:"
 msgstr "Error:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:792
+#: ../libempathy-gtk/empathy-contact-widget.c:931
 #: ../libempathy-gtk/empathy-individual-widget.c:518
 msgid "Vertical Error (meters):"
 msgstr "Error vertical (metros):"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:794
+#: ../libempathy-gtk/empathy-contact-widget.c:933
 #: ../libempathy-gtk/empathy-individual-widget.c:520
 msgid "Horizontal Error (meters):"
 msgstr "Error horizontal (metros):"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:796
+#: ../libempathy-gtk/empathy-contact-widget.c:935
 #: ../libempathy-gtk/empathy-individual-widget.c:522
 msgid "Speed:"
 msgstr "Velocidad:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:798
+#: ../libempathy-gtk/empathy-contact-widget.c:937
 #: ../libempathy-gtk/empathy-individual-widget.c:524
 msgid "Bearing:"
 msgstr "Retardo:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:800
+#: ../libempathy-gtk/empathy-contact-widget.c:939
 #: ../libempathy-gtk/empathy-individual-widget.c:526
 msgid "Climb Speed:"
 msgstr "Velocidad de ascenso:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:802
+#: ../libempathy-gtk/empathy-contact-widget.c:941
 #: ../libempathy-gtk/empathy-individual-widget.c:528
 msgid "Last Updated on:"
 msgstr "Actualizado por última vez:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:804
+#: ../libempathy-gtk/empathy-contact-widget.c:943
 #: ../libempathy-gtk/empathy-individual-widget.c:530
 msgid "Longitude:"
 msgstr "Longitud_"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:806
+#: ../libempathy-gtk/empathy-contact-widget.c:945
 #: ../libempathy-gtk/empathy-individual-widget.c:532
 msgid "Latitude:"
 msgstr "Latitud:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:808
+#: ../libempathy-gtk/empathy-contact-widget.c:947
 #: ../libempathy-gtk/empathy-individual-widget.c:534
 msgid "Altitude:"
 msgstr "Altitud:"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:861
-#: ../libempathy-gtk/empathy-contact-widget.c:876
+#: ../libempathy-gtk/empathy-contact-widget.c:1000
+#: ../libempathy-gtk/empathy-contact-widget.c:1015
 #: ../libempathy-gtk/empathy-individual-widget.c:616
 #: ../libempathy-gtk/empathy-individual-widget.c:631
 #: ../src/empathy-preferences.ui.h:12
@@ -1932,23 +1970,23 @@ msgid "Location"
 msgstr "Ubicación geográfica"
 
 #. translators: format is "Location, $date"
-#: ../libempathy-gtk/empathy-contact-widget.c:878
+#: ../libempathy-gtk/empathy-contact-widget.c:1017
 #: ../libempathy-gtk/empathy-individual-widget.c:633
 #, c-format
 msgid "%s, %s"
 msgstr "%s, %s"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:930
+#: ../libempathy-gtk/empathy-contact-widget.c:1069
 #: ../libempathy-gtk/empathy-individual-widget.c:682
 msgid "%B %e, %Y at %R UTC"
 msgstr "%e de %B de %Y a las %R UTC"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:1012
+#: ../libempathy-gtk/empathy-contact-widget.c:1151
 #: ../libempathy-gtk/empathy-individual-widget.c:917
 msgid "Save Avatar"
 msgstr "Guardar avatar"
 
-#: ../libempathy-gtk/empathy-contact-widget.c:1068
+#: ../libempathy-gtk/empathy-contact-widget.c:1207
 #: ../libempathy-gtk/empathy-individual-widget.c:975
 msgid "Unable to save avatar"
 msgstr "No se pudo guardar el avatar"
@@ -2225,17 +2263,17 @@ msgid "New Conversation"
 msgstr "Conversación nueva"
 
 #. add video toggle
-#: ../libempathy-gtk/empathy-new-call-dialog.c:253
+#: ../libempathy-gtk/empathy-new-call-dialog.c:254
 msgid "Send _Video"
 msgstr "Enviar _vídeo"
 
 #. add chat button
-#: ../libempathy-gtk/empathy-new-call-dialog.c:261
+#: ../libempathy-gtk/empathy-new-call-dialog.c:262
 msgid "C_all"
 msgstr "_Llamar"
 
 #. Tweak the dialog
-#: ../libempathy-gtk/empathy-new-call-dialog.c:271
+#: ../libempathy-gtk/empathy-new-call-dialog.c:272
 msgid "New Call"
 msgstr "Llamada nueva"
 
@@ -2362,7 +2400,7 @@ msgstr "Guardar mensaje de estado _nuevo"
 msgid "Saved Status Messages"
 msgstr "Mensajes de estado guardados"
 
-#: ../libempathy-gtk/empathy-theme-adium.c:1462
+#: ../libempathy-gtk/empathy-theme-adium.c:1468
 msgid "Normal"
 msgstr "Normal"
 
@@ -3432,90 +3470,90 @@ msgstr "Autoconectar"
 msgid "Manage Favorite Rooms"
 msgstr "Gestionar salas favoritas"
 
-#: ../src/empathy-event-manager.c:504
+#: ../src/empathy-event-manager.c:505
 msgid "Incoming video call"
 msgstr "Llamada de vídeo entrante"
 
-#: ../src/empathy-event-manager.c:504
+#: ../src/empathy-event-manager.c:505
 msgid "Incoming call"
 msgstr "Llamada entrante"
 
-#: ../src/empathy-event-manager.c:508
+#: ../src/empathy-event-manager.c:509
 #, c-format
 msgid "%s is video calling you. Do you want to answer?"
 msgstr "%s le está llamando con vídeo. ¿Quiere responder?"
 
-#: ../src/empathy-event-manager.c:509
+#: ../src/empathy-event-manager.c:510
 #, c-format
 msgid "%s is calling you. Do you want to answer?"
 msgstr "%s le está llamando. ¿Quiere responder?"
 
-#: ../src/empathy-event-manager.c:512 ../src/empathy-event-manager.c:661
+#: ../src/empathy-event-manager.c:513 ../src/empathy-event-manager.c:662
 #, c-format
 msgid "Incoming call from %s"
 msgstr "Llamada entrante de %s"
 
-#: ../src/empathy-event-manager.c:537
+#: ../src/empathy-event-manager.c:538
 msgid "_Reject"
 msgstr "_Rechazar"
 
-#: ../src/empathy-event-manager.c:543
+#: ../src/empathy-event-manager.c:544
 msgid "_Answer"
 msgstr "Re_spuesta"
 
-#: ../src/empathy-event-manager.c:661
+#: ../src/empathy-event-manager.c:662
 #, c-format
 msgid "Incoming video call from %s"
 msgstr "Llamada de vídeo entrante de %s"
 
-#: ../src/empathy-event-manager.c:734
+#: ../src/empathy-event-manager.c:735
 msgid "Room invitation"
 msgstr "Invitación a una sala"
 
-#: ../src/empathy-event-manager.c:736
+#: ../src/empathy-event-manager.c:737
 #, c-format
 msgid "Invitation to join %s"
 msgstr "Invitación para unirse a %s"
 
-#: ../src/empathy-event-manager.c:743
+#: ../src/empathy-event-manager.c:744
 #, c-format
 msgid "%s is inviting you to join %s"
 msgstr "%s le está invitando a unirse a %s"
 
-#: ../src/empathy-event-manager.c:751
+#: ../src/empathy-event-manager.c:752
 msgid "_Decline"
 msgstr "_Rechazar"
 
-#: ../src/empathy-event-manager.c:756
+#: ../src/empathy-event-manager.c:757
 #: ../src/empathy-new-chatroom-dialog.ui.h:7
 msgid "_Join"
 msgstr "_Unirse"
 
-#: ../src/empathy-event-manager.c:783
+#: ../src/empathy-event-manager.c:784
 #, c-format
 msgid "%s invited you to join %s"
 msgstr "%s le ha invitado a unirse a %s"
 
-#: ../src/empathy-event-manager.c:789
+#: ../src/empathy-event-manager.c:790
 #, c-format
 msgid "You have been invited to join %s"
 msgstr "Le han invitado a unirse a %s"
 
-#: ../src/empathy-event-manager.c:840
+#: ../src/empathy-event-manager.c:841
 #, c-format
 msgid "Incoming file transfer from %s"
 msgstr "Transferencia de archivo entrante de %s"
 
-#: ../src/empathy-event-manager.c:1010 ../src/empathy-main-window.c:377
+#: ../src/empathy-event-manager.c:1011 ../src/empathy-main-window.c:377
 msgid "Password required"
 msgstr "Se requiere una contraseña"
 
-#: ../src/empathy-event-manager.c:1066
+#: ../src/empathy-event-manager.c:1067
 #, c-format
 msgid "%s would like permission to see when you are online"
 msgstr "%s quiere permiso para ver cuándo está en línea"
 
-#: ../src/empathy-event-manager.c:1070
+#: ../src/empathy-event-manager.c:1071
 #, c-format
 msgid ""
 "\n"
index 93d9df8d7c4bd8d791784f683b3dced74e262892..d8a75cb0cf0b9046ab82b29eb86ca8d0fbc56e89 100644 (file)
@@ -794,6 +794,7 @@ chat_window_update_chat_tab (EmpathyChat *chat)
 static void
 chat_window_chat_notify_cb (EmpathyChat *chat)
 {
+       EmpathyChatWindow *window;
        EmpathyContact *old_remote_contact;
        EmpathyContact *remote_contact = NULL;
 
@@ -820,6 +821,11 @@ chat_window_chat_notify_cb (EmpathyChat *chat)
        }
 
        chat_window_update_chat_tab (chat);
+
+       window = chat_window_find_chat (chat);
+       if (window != NULL) {
+               chat_window_update (window, FALSE);
+       }
 }
 
 static void
@@ -1547,7 +1553,7 @@ chat_window_command_part (EmpathyChat *chat,
                return;
        }
        chat_to_be_parted = empathy_chat_window_find_chat (
-               empathy_chat_get_account (chat), strv[1]);
+               empathy_chat_get_account (chat), strv[1], FALSE);
 
        if (chat_to_be_parted != NULL) {
                /* Found a chatroom matching the specified ID */
@@ -2326,6 +2332,9 @@ empathy_chat_window_add_chat (EmpathyChatWindow *window,
        g_signal_connect (chat, "notify::n-messages-sending",
                          G_CALLBACK (chat_window_chat_notify_cb),
                          NULL);
+       g_signal_connect (chat, "notify::nb-unread-messages",
+                         G_CALLBACK (chat_window_chat_notify_cb),
+                         NULL);
        chat_window_chat_notify_cb (chat);
 
        gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);