diff options
Diffstat (limited to 'libempathy-gtk')
-rw-r--r-- | libempathy-gtk/empathy-chat-view.c | 11 | ||||
-rw-r--r-- | libempathy-gtk/empathy-chat-view.h | 4 | ||||
-rw-r--r-- | libempathy-gtk/empathy-chat.c | 125 | ||||
-rw-r--r-- | libempathy-gtk/empathy-contact-widget.c | 159 | ||||
-rw-r--r-- | libempathy-gtk/empathy-theme-adium.c | 163 |
5 files changed, 420 insertions, 42 deletions
diff --git a/libempathy-gtk/empathy-chat-view.c b/libempathy-gtk/empathy-chat-view.c index 097215cfd..43d89dd78 100644 --- a/libempathy-gtk/empathy-chat-view.c +++ b/libempathy-gtk/empathy-chat-view.c @@ -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); + } +} + diff --git a/libempathy-gtk/empathy-chat-view.h b/libempathy-gtk/empathy-chat-view.h index 1af0721a3..73245c422 100644 --- a/libempathy-gtk/empathy-chat-view.h +++ b/libempathy-gtk/empathy-chat-view.h @@ -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 diff --git a/libempathy-gtk/empathy-chat.c b/libempathy-gtk/empathy-chat.c index 25203b46e..79411e74d 100644 --- a/libempathy-gtk/empathy-chat.c +++ b/libempathy-gtk/empathy-chat.c @@ -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); } @@ -1257,6 +1348,20 @@ chat_message_received_cb (EmpathyTpChat *tp_chat, } 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, TpChannelTextSendError error_code, @@ -2845,6 +2950,8 @@ chat_finalize (GObject *object) g_signal_handlers_disconnect_by_func (priv->tp_chat, chat_message_received_cb, chat); g_signal_handlers_disconnect_by_func (priv->tp_chat, + chat_message_acknowledged_cb, chat); + g_signal_handlers_disconnect_by_func (priv->tp_chat, chat_send_error_cb, chat); g_signal_handlers_disconnect_by_func (priv->tp_chat, chat_state_changed_cb, chat); @@ -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 */ diff --git a/libempathy-gtk/empathy-contact-widget.c b/libempathy-gtk/empathy-contact-widget.c index c4e3748b4..14042bf61 100644 --- a/libempathy-gtk/empathy-contact-widget.c +++ b/libempathy-gtk/empathy-contact-widget.c @@ -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; } diff --git a/libempathy-gtk/empathy-theme-adium.c b/libempathy-gtk/empathy-theme-adium.c index 06410c528..b62b017c9 100644 --- a/libempathy-gtk/empathy-theme-adium.c +++ b/libempathy-gtk/empathy-theme-adium.c @@ -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++) { @@ -632,12 +613,43 @@ 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) { 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) { @@ -927,16 +953,93 @@ theme_adium_copy_clipboard (EmpathyChatView *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); } |