X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=libempathy-gtk%2Fempathy-contact-chooser.c;h=4a4e4b4fad8eaabdcb14471b227caa4f80735015;hp=38956bac18ff8ac1cdacd25d876baa3c70b207b2;hb=4edb0bd465045faf92efdbe835c38d78c5e8208b;hpb=820b5479bd14c6b408a3818f9bc287ad0a3e2fb8 diff --git a/libempathy-gtk/empathy-contact-chooser.c b/libempathy-gtk/empathy-contact-chooser.c index 38956bac..4a4e4b4f 100644 --- a/libempathy-gtk/empathy-contact-chooser.c +++ b/libempathy-gtk/empathy-contact-chooser.c @@ -9,24 +9,23 @@ * Danielle Madeley */ -#include -#include - +#include "config.h" #include "empathy-contact-chooser.h" -#include -#include +#include + +#include "empathy-client-factory.h" +#include "empathy-individual-store-manager.h" +#include "empathy-individual-view.h" +#include "empathy-ui-utils.h" +#include "empathy-utils.h" G_DEFINE_TYPE (EmpathyContactChooser, empathy_contact_chooser, GTK_TYPE_BOX); -enum -{ - PROP_TP_CHAT = 1 -}; - enum { SIG_SELECTION_CHANGED, + SIG_ACTIVATE, LAST_SIGNAL }; @@ -36,11 +35,12 @@ typedef struct _AddTemporaryIndividualCtx AddTemporaryIndividualCtx; struct _EmpathyContactChooserPrivate { - EmpathyTpChat *tp_chat; TpAccountManager *account_mgr; EmpathyIndividualStore *store; EmpathyIndividualView *view; + GtkWidget *search_entry; + GtkWidget *scroll_view; GPtrArray *search_words; gchar *search_str; @@ -48,48 +48,13 @@ struct _EmpathyContactChooserPrivate /* Context representing the FolksIndividual which are added because of the * current search from the user. */ AddTemporaryIndividualCtx *add_temp_ctx; -}; - -static void -contact_chooser_get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec) -{ - EmpathyContactChooser *self = (EmpathyContactChooser *) - object; - switch (param_id) - { - case PROP_TP_CHAT: - g_value_set_object (value, self->priv->tp_chat); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); - break; - }; -} + EmpathyContactChooserFilterFunc filter_func; + gpointer filter_data; -static void -contact_chooser_set_property (GObject *object, - guint param_id, - const GValue *value, - GParamSpec *pspec) -{ - EmpathyContactChooser *self = (EmpathyContactChooser *) - object; - - switch (param_id) - { - case PROP_TP_CHAT: - g_assert (self->priv->tp_chat == NULL); /* construct-only */ - self->priv->tp_chat = g_value_dup_object (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); - break; - }; -} + /* list of reffed TpContact */ + GList *tp_contacts; +}; struct _AddTemporaryIndividualCtx { @@ -136,13 +101,15 @@ contact_chooser_dispose (GObject *object) tp_clear_pointer (&self->priv->add_temp_ctx, add_temporary_individual_ctx_free); - tp_clear_object (&self->priv->tp_chat); tp_clear_object (&self->priv->store); tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref); tp_clear_pointer (&self->priv->search_str, g_free); tp_clear_object (&self->priv->account_mgr); + g_list_free_full (self->priv->tp_contacts, g_object_unref); + self->priv->tp_contacts = NULL; + G_OBJECT_CLASS (empathy_contact_chooser_parent_class)->dispose ( object); } @@ -153,27 +120,28 @@ empathy_contact_chooser_class_init ( { GObjectClass *object_class = G_OBJECT_CLASS (klass); - object_class->get_property = contact_chooser_get_property; - object_class->set_property = contact_chooser_set_property; object_class->dispose = contact_chooser_dispose; g_type_class_add_private (object_class, sizeof (EmpathyContactChooserPrivate)); - g_object_class_install_property (object_class, - PROP_TP_CHAT, - g_param_spec_object ("tp-chat", "EmpathyTpChat", "EmpathyTpChat", - EMPATHY_TYPE_TP_CHAT, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - signals[SIG_SELECTION_CHANGED] = g_signal_new ("selection-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, - g_cclosure_marshal_VOID__OBJECT, + g_cclosure_marshal_generic, G_TYPE_NONE, 1, FOLKS_TYPE_INDIVIDUAL); + + signals[SIG_ACTIVATE] = g_signal_new ("activate", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 0); } static void @@ -189,47 +157,6 @@ view_selection_changed_cb (GtkWidget *treeview, tp_clear_object (&individual); } -/* Return the TpContact of @individual which is on the same connection as the - * EmpathyTpChat */ -static TpContact * -get_tp_contact_for_chat (EmpathyContactChooser *self, - FolksIndividual *individual) -{ - TpContact *contact = NULL; - TpConnection *chat_conn; - GeeSet *personas; - GeeIterator *iter; - - chat_conn = tp_channel_borrow_connection (TP_CHANNEL (self->priv->tp_chat)); - - personas = folks_individual_get_personas (individual); - iter = gee_iterable_iterator (GEE_ITERABLE (personas)); - while (contact == FALSE && gee_iterator_next (iter)) - { - TpfPersona *persona = gee_iterator_get (iter); - TpConnection *contact_conn; - TpContact *contact_cur = NULL; - - if (TPF_IS_PERSONA (persona)) - { - contact_cur = tpf_persona_get_contact (persona); - if (contact_cur != NULL) - { - contact_conn = tp_contact_get_connection (contact_cur); - - if (!tp_strdiff (tp_proxy_get_object_path (contact_conn), - tp_proxy_get_object_path (chat_conn))) - contact = contact_cur; - } - } - - g_clear_object (&persona); - } - g_clear_object (&iter); - - return contact; -} - static gboolean filter_func (GtkTreeModel *model, GtkTreeIter *iter, @@ -237,10 +164,9 @@ filter_func (GtkTreeModel *model, { EmpathyContactChooser *self = user_data; FolksIndividual *individual; - TpContact *contact; gboolean is_online; - GList *members, *l; gboolean display = FALSE; + gboolean searching = FALSE; gtk_tree_model_get (model, iter, EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, @@ -250,104 +176,93 @@ filter_func (GtkTreeModel *model, if (individual == NULL) goto out; - if (self->priv->search_words == NULL) - { - /* Not searching, display online contacts */ - if (!is_online) - goto out; - } - else + if (self->priv->search_words != NULL) { + searching = TRUE; + + /* Filter out the contact if we are searching and it doesn't match */ if (!empathy_individual_match_string (individual, self->priv->search_str, self->priv->search_words)) goto out; } - /* Filter out individuals not having a persona on the same connection as the - * EmpathyTpChat. */ - contact = get_tp_contact_for_chat (self, individual); - - if (contact == NULL) - goto out; - - /* Filter out contacts which are already in the chat */ - members = empathy_contact_list_get_members (EMPATHY_CONTACT_LIST ( - self->priv->tp_chat)); - - display = TRUE; - - for (l = members; l != NULL; l = g_list_next (l)) - { - EmpathyContact *member = l->data; - TpHandle handle; - - /* Try to get the non-channel specific handle. */ - handle = tp_channel_group_get_handle_owner ( - TP_CHANNEL (self->priv->tp_chat), - empathy_contact_get_handle (member)); - if (handle == 0) - handle = empathy_contact_get_handle (member); - - if (handle == tp_contact_get_handle (contact)) - { - display = FALSE; - break; - } - } - - g_list_free_full (members, g_object_unref); - + if (self->priv->filter_func == NULL) + display = TRUE; + else + display = self->priv->filter_func (self, individual, is_online, searching, + self->priv->filter_data); out: tp_clear_object (&individual); return display; } static void -get_contacts_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) +contact_capabilities_changed (TpContact *contact, + GParamSpec *pspec, + EmpathyContactChooser *self) { - EmpathyContactChooser *self = - (EmpathyContactChooser *) weak_object; - AddTemporaryIndividualCtx *ctx = user_data; - TpAccount *account; - TpfPersonaStore *store; + empathy_individual_view_refilter (self->priv->view); +} + +static void +get_contacts_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + TpWeakRef *wr = user_data; + AddTemporaryIndividualCtx *ctx; + EmpathyContactChooser *self; + GError *error = NULL; FolksIndividual *individual; - TpfPersona *persona_new; - GeeSet *personas; + TpContact *contact; + EmpathyContact *emp_contact = NULL; + + self = tp_weak_ref_dup_object (wr); + if (self == NULL) + goto out; + + ctx = tp_weak_ref_get_user_data (wr); + + emp_contact = empathy_client_factory_dup_contact_by_id_finish ( + EMPATHY_CLIENT_FACTORY (source), result, &error); + if (emp_contact == NULL) + goto out; + + contact = empathy_contact_get_tp_contact (emp_contact); if (self->priv->add_temp_ctx != ctx) /* another request has been started */ - return; - - if (n_contacts != 1) - return; + goto out; - account = g_object_get_data (G_OBJECT (connection), "account"); + individual = empathy_ensure_individual_from_tp_contact (contact); + if (individual == NULL) + goto out; - store = tpf_persona_store_new (account); - personas = GEE_SET ( - gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref, g_object_unref, - g_direct_hash, g_direct_equal)); - persona_new = tpf_persona_new (contacts[0], store); - gee_collection_add (GEE_COLLECTION (personas), - tpf_persona_new (contacts[0], store)); + /* tp-glib will unref the TpContact once we return from this callback + * but folks expect us to keep a reference on the TpContact. + * Ideally folks shouldn't force us to do that: bgo #666580 */ + self->priv->tp_contacts = g_list_prepend (self->priv->tp_contacts, + g_object_ref (contact)); - individual = folks_individual_new (personas); + /* listen for updates to the capabilities */ + tp_g_signal_connect_object (contact, "notify::capabilities", + G_CALLBACK (contact_capabilities_changed), self, 0); /* Pass ownership to the list */ ctx->individuals = g_list_prepend (ctx->individuals, individual); individual_store_add_individual_and_connect (self->priv->store, individual); - g_clear_object (&persona_new); - g_clear_object (&personas); - g_object_unref (store); + /* if nothing is selected, select the first matching node */ + if (!gtk_tree_selection_get_selected ( + gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view)), + NULL, NULL)) + empathy_individual_view_select_first (self->priv->view); + +out: + g_clear_object (&emp_contact); + g_clear_object (&self); + tp_weak_ref_destroy (wr); } static void @@ -365,30 +280,27 @@ add_temporary_individuals (EmpathyContactChooser *self, self->priv->add_temp_ctx = add_temporary_individual_ctx_new (self); /* Try to add an individual for each connected account */ - accounts = tp_account_manager_get_valid_accounts (self->priv->account_mgr); + accounts = tp_account_manager_dup_valid_accounts (self->priv->account_mgr); for (l = accounts; l != NULL; l = g_list_next (l)) { TpAccount *account = l->data; TpConnection *conn; - TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS, - TP_CONTACT_FEATURE_AVATAR_DATA, - TP_CONTACT_FEATURE_PRESENCE, - TP_CONTACT_FEATURE_CAPABILITIES }; + EmpathyClientFactory *factory; conn = tp_account_get_connection (account); if (conn == NULL) continue; - /* One day we'll have tp_connection_get_account()... */ - g_object_set_data_full (G_OBJECT (conn), "account", - g_object_ref (account), g_object_unref); + factory = empathy_client_factory_dup (); + + empathy_client_factory_dup_contact_by_id_async (factory, conn, id, + get_contacts_cb, + tp_weak_ref_new (self, self->priv->add_temp_ctx, NULL)); - tp_connection_get_contacts_by_id (conn, 1, &id, G_N_ELEMENTS (features), - features, get_contacts_cb, self->priv->add_temp_ctx, NULL, - G_OBJECT (self)); + g_object_unref (factory); } - g_list_free (accounts); + g_list_free_full (accounts, g_object_unref); } static void @@ -402,7 +314,7 @@ search_text_changed (GtkEntry *entry, id = gtk_entry_get_text (entry); - self->priv->search_words = empathy_live_search_strip_utf8_string (id); + self->priv->search_words = tpaw_live_search_strip_utf8_string (id); self->priv->search_str = g_strdup (id); add_temporary_individuals (self, id); @@ -410,13 +322,81 @@ search_text_changed (GtkEntry *entry, empathy_individual_view_refilter (self->priv->view); } +static void +search_activate_cb (GtkEntry *entry, + EmpathyContactChooser *self) +{ + g_signal_emit (self, signals[SIG_ACTIVATE], 0); +} + +static void +view_activate_cb (GtkTreeView *view, + GtkTreePath *path, + GtkTreeViewColumn *column, + EmpathyContactChooser *self) +{ + g_signal_emit (self, signals[SIG_ACTIVATE], 0); +} + +static gboolean +search_key_press_cb (GtkEntry *entry, + GdkEventKey *event, + EmpathyContactChooser *self) +{ + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + + if (event->state != 0) + return FALSE; + + switch (event->keyval) + { + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + break; + + default: + return FALSE; + } + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view)); + + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) + return TRUE; + + switch (event->keyval) + { + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + if (!gtk_tree_model_iter_next (model, &iter)) + return TRUE; + + break; + + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + if (!gtk_tree_model_iter_previous (model, &iter)) + return TRUE; + + break; + + default: + g_assert_not_reached (); + } + + gtk_tree_selection_select_iter (selection, &iter); + + return TRUE; +} + static void empathy_contact_chooser_init (EmpathyContactChooser *self) { EmpathyIndividualManager *mgr; GtkTreeSelection *selection; - GtkWidget *scroll; - GtkWidget *search_entry; GQuark features[] = { TP_ACCOUNT_MANAGER_FEATURE_CORE, 0 }; self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EMPATHY_TYPE_CONTACT_CHOOSER, @@ -431,16 +411,23 @@ empathy_contact_chooser_init (EmpathyContactChooser *self) tp_proxy_prepare_async (self->priv->account_mgr, features, NULL, NULL); /* Search entry */ - search_entry = gtk_entry_new (); - gtk_box_pack_start (GTK_BOX (self), search_entry, FALSE, TRUE, 6); - gtk_widget_show (search_entry); + self->priv->search_entry = gtk_search_entry_new (); + gtk_entry_set_placeholder_text ( GTK_ENTRY(self->priv->search_entry), + _("Type to search a contact…")); + gtk_box_pack_start (GTK_BOX (self), self->priv->search_entry, FALSE, TRUE, 0); + gtk_widget_show (self->priv->search_entry); - g_signal_connect (search_entry, "changed", + g_signal_connect (self->priv->search_entry, "changed", G_CALLBACK (search_text_changed), self); + g_signal_connect (self->priv->search_entry, "activate", + G_CALLBACK (search_activate_cb), self); + g_signal_connect (self->priv->search_entry, "key-press-event", + G_CALLBACK (search_key_press_cb), self); /* Add the treeview */ mgr = empathy_individual_manager_dup_singleton (); - self->priv->store = empathy_individual_store_new (mgr); + self->priv->store = EMPATHY_INDIVIDUAL_STORE ( + empathy_individual_store_manager_new (mgr)); g_object_unref (mgr); empathy_individual_store_set_show_groups (self->priv->store, FALSE); @@ -455,39 +442,57 @@ empathy_contact_chooser_init (EmpathyContactChooser *self) g_signal_connect (selection, "changed", G_CALLBACK (view_selection_changed_cb), self); + g_signal_connect (self->priv->view, "row-activated", + G_CALLBACK (view_activate_cb), self); - scroll = gtk_scrolled_window_new (NULL, NULL); + self->priv->scroll_view = gtk_scrolled_window_new (NULL, NULL); - gtk_container_add (GTK_CONTAINER (scroll), GTK_WIDGET (self->priv->view)); + gtk_container_add (GTK_CONTAINER (self->priv->scroll_view), + GTK_WIDGET (self->priv->view)); - gtk_box_pack_start (GTK_BOX (self), scroll, TRUE, TRUE, 6); + gtk_box_pack_start (GTK_BOX (self), self->priv->scroll_view, TRUE, TRUE, 0); gtk_widget_show (GTK_WIDGET (self->priv->view)); - gtk_widget_show (scroll); + gtk_widget_show (self->priv->scroll_view); } GtkWidget * -empathy_contact_chooser_new (EmpathyTpChat *tp_chat) +empathy_contact_chooser_new (void) { - g_return_val_if_fail (EMPATHY_IS_TP_CHAT (tp_chat), NULL); - return g_object_new (EMPATHY_TYPE_CONTACT_CHOOSER, "orientation", GTK_ORIENTATION_VERTICAL, - "tp-chat", tp_chat, NULL); } -TpContact * -empathy_contact_chooser_get_selected (EmpathyContactChooser *self) +/* Note that because of bgo #666580 the returned invidivdual is valid until + * @self is destroyed. To keep it around after that, the TpContact associated + * with the individual needs to be manually reffed. */ +FolksIndividual * +empathy_contact_chooser_dup_selected (EmpathyContactChooser *self) { - FolksIndividual *individual; - TpContact *contact; + return empathy_individual_view_dup_selected (self->priv->view); +} - individual = empathy_individual_view_dup_selected (self->priv->view); - if (individual == NULL) - return NULL; +void +empathy_contact_chooser_set_filter_func (EmpathyContactChooser *self, + EmpathyContactChooserFilterFunc func, + gpointer user_data) +{ + g_assert (self->priv->filter_func == NULL); - contact = get_tp_contact_for_chat (self, individual); + self->priv->filter_func = func; + self->priv->filter_data = user_data; +} - g_object_unref (individual); - return contact; +void +empathy_contact_chooser_show_search_entry (EmpathyContactChooser *self, + gboolean show) +{ + gtk_widget_set_visible (self->priv->search_entry, show); +} + +void +empathy_contact_chooser_show_tree_view (EmpathyContactChooser *self, + gboolean show) +{ + gtk_widget_set_visible (GTK_WIDGET (self->priv->scroll_view), show); }