#include <telepathy-glib/account-manager.h>
#include <telepathy-glib/util.h>
-#include <libempathy/empathy-call-factory.h>
+#include <libempathy/empathy-client-factory.h>
#include <libempathy/empathy-tp-contact-factory.h>
#include <libempathy/empathy-contact-list.h>
#include <libempathy/empathy-contact-groups.h>
-#include <libempathy/empathy-dispatcher.h>
+#include <libempathy/empathy-request-util.h>
#include <libempathy/empathy-utils.h>
#include "empathy-contact-list-view.h"
#include "empathy-cell-renderer-activatable.h"
#include "empathy-ui-utils.h"
#include "empathy-gtk-enum-types.h"
-#include "empathy-gtk-marshal.h"
#define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
#include <libempathy/empathy-debug.h>
{ "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
};
-static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
-static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
-
enum {
DRAG_CONTACT_RECEIVED,
LAST_SIGNAL
g_assert (live != NULL);
/* check alias name */
- str = empathy_contact_get_name (contact);
+ str = empathy_contact_get_alias (contact);
if (empathy_live_search_match (live, str))
return TRUE;
list = empathy_contact_list_store_get_list_iface (priv->store);
- if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
- /* Mark contact as favourite */
- empathy_contact_list_add_to_favourites (list, contact);
- return;
- }
-
- if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
- /* Remove contact as favourite */
- empathy_contact_list_remove_from_favourites (list, contact);
- /* Don't try to remove it */
- g_free (data->old_group);
- data->old_group = NULL;
- }
-
if (data->new_group) {
empathy_contact_list_add_to_group (list, contact, data->new_group);
}
}
}
-static gboolean
-group_can_be_modified (const gchar *name,
- gboolean is_fake_group,
- gboolean adding)
-{
- /* Real groups can always be modified */
- if (!is_fake_group)
- return TRUE;
-
- /* The favorite fake group can be modified so users can
- * add/remove favorites using DnD */
- if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
- return TRUE;
-
- /* We can remove contacts from the 'ungrouped' fake group */
- if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
- return TRUE;
-
- return FALSE;
-}
-
static gboolean
contact_list_view_contact_drag_received (GtkWidget *view,
GdkDragContext *context,
GtkSelectionData *selection)
{
EmpathyContactListViewPriv *priv;
- TpAccountManager *account_manager;
- TpConnection *connection;
- TpAccount *account;
+ EmpathyClientFactory *factory;
+ TpConnection *connection = NULL;
+ TpAccount *account = NULL;
DndGetContactData *data;
GtkTreePath *source_path;
const gchar *sel_data;
const gchar *contact_id = NULL;
gchar *new_group = NULL;
gchar *old_group = NULL;
- gboolean success = TRUE;
gboolean new_group_is_fake, old_group_is_fake = TRUE;
priv = GET_PRIV (view);
new_group = empathy_contact_list_store_get_parent_group (model,
path, NULL, &new_group_is_fake);
- if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
- return FALSE;
-
/* Get source group information. */
if (priv->drag_row) {
source_path = gtk_tree_row_reference_get_path (priv->drag_row);
}
}
- if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
- return FALSE;
-
if (!tp_strdiff (old_group, new_group)) {
g_free (new_group);
g_free (old_group);
return FALSE;
}
- account_manager = tp_account_manager_dup ();
+ factory = empathy_client_factory_dup ();
strv = g_strsplit (sel_data, ":", 2);
if (g_strv_length (strv) == 2) {
account_id = strv[0];
contact_id = strv[1];
- account = tp_account_manager_ensure_account (account_manager, account_id);
+ account = tp_simple_client_factory_ensure_account (
+ TP_SIMPLE_CLIENT_FACTORY (factory),
+ account_id, NULL, NULL);
}
if (account) {
connection = tp_account_get_connection (account);
if (!connection) {
DEBUG ("Failed to get connection for account '%s'", account_id);
- success = FALSE;
g_free (new_group);
g_free (old_group);
- g_object_unref (account_manager);
+ g_object_unref (factory);
return FALSE;
}
data = g_slice_new0 (DndGetContactData);
data->new_group = new_group;
data->old_group = old_group;
- data->action = context->action;
+ data->action = gdk_drag_context_get_selected_action (context);
/* FIXME: We should probably wait for the cb before calling
* gtk_drag_finish */
data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
G_OBJECT (view));
g_strfreev (strv);
- g_object_unref (account_manager);
+ g_object_unref (factory);
return TRUE;
}
priv = GET_PRIV (widget);
- GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
- context);
-
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
return;
}
+ GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
+ context);
+
path = gtk_tree_model_get_path (model, &iter);
priv->drag_row = gtk_tree_row_reference_new (model, path);
gtk_tree_path_free (path);
g_object_unref (contact);
str = g_strconcat (account_id, ":", contact_id, NULL);
- switch (info) {
- case DND_DRAG_TYPE_CONTACT_ID:
- gtk_selection_data_set (selection, drag_atoms_source[info], 8,
+ if (info == DND_DRAG_TYPE_CONTACT_ID) {
+ gtk_selection_data_set (selection,
+ gdk_atom_intern ("text/contact-id", FALSE), 8,
(guchar *) str, strlen (str) + 1);
- break;
}
g_free (str);
guint32 time;
} MenuPopupData;
+static void
+menu_deactivate_cb (GtkMenuShell *menushell,
+ gpointer user_data)
+{
+ /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
+ g_signal_handlers_disconnect_by_func (menushell,
+ menu_deactivate_cb, user_data);
+
+ gtk_menu_detach (GTK_MENU (menushell));
+}
+
static gboolean
contact_list_view_popup_menu_idle_cb (gpointer user_data)
{
}
if (menu) {
- g_signal_connect (menu, "deactivate",
- G_CALLBACK (gtk_menu_detach), NULL);
gtk_menu_attach_to_widget (GTK_MENU (menu),
GTK_WIDGET (data->view), NULL);
gtk_widget_show (menu);
gtk_menu_popup (GTK_MENU (menu),
NULL, NULL, NULL, NULL,
data->button, data->time);
- g_object_ref_sink (menu);
- g_object_unref (menu);
+
+ /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
+ * floating ref. We can either wait that the treeview releases its ref
+ * when it will be destroyed (when leaving Empathy) or explicitely
+ * detach the menu when it's not displayed any more.
+ * We go for the latter as we don't want to keep useless menus in memory
+ * during the whole lifetime of Empathy. */
+ g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
+ NULL);
}
g_slice_free (MenuPopupData, data);
GdkEventKey *event,
gpointer user_data)
{
- if (event->keyval == GDK_Menu) {
+ if (event->keyval == GDK_KEY_Menu) {
MenuPopupData *data;
data = g_slice_new (MenuPopupData);
if (contact) {
DEBUG ("Starting a chat");
- empathy_dispatcher_chat_with_contact (contact,
- gtk_get_current_event_time (), NULL, NULL);
+ empathy_chat_with_contact (contact,
+ empathy_get_current_action_time ());
g_object_unref (contact);
}
}
event = (GdkEventButton *) gtk_get_current_event ();
- menu = gtk_menu_new ();
+ menu = empathy_context_menu_new (GTK_WIDGET (view));
shell = GTK_MENU_SHELL (menu);
/* audio */
gtk_menu_shell_append (shell, item);
gtk_widget_show (item);
- g_signal_connect (menu, "deactivate",
- G_CALLBACK (gtk_menu_detach), NULL);
- gtk_menu_attach_to_widget (GTK_MENU (menu),
- GTK_WIDGET (view), NULL);
gtk_widget_show (menu);
gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
event->button, event->time);
- g_object_ref_sink (menu);
- g_object_unref (menu);
g_object_unref (contact);
}
gboolean is_group,
gboolean is_active)
{
- GdkColor color;
- GtkStyle *style;
+ if (!is_group && is_active) {
+ GdkRGBA color;
+ GtkStyleContext *style;
- style = gtk_widget_get_style (GTK_WIDGET (view));
+ style = gtk_widget_get_style_context (GTK_WIDGET (view));
- if (!is_group && is_active) {
- color = style->bg[GTK_STATE_SELECTED];
+ gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
+ &color);
/* Here we take the current theme colour and add it to
* the colour for white and average the two. This
* gives a colour which is inline with the theme but
* slightly whiter.
*/
- color.red = (color.red + (style->white).red) / 2;
- color.green = (color.green + (style->white).green) / 2;
- color.blue = (color.blue + (style->white).blue) / 2;
+ empathy_make_color_whiter (&color);
g_object_set (cell,
- "cell-background-gdk", &color,
+ "cell-background-rgba", &color,
NULL);
} else {
g_object_set (cell,
- "cell-background-gdk", NULL,
+ "cell-background-rgba", NULL,
NULL);
}
}
if (!is_group)
goto out;
- if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
- pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
- GTK_ICON_SIZE_MENU);
- }
- else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
- pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
- GTK_ICON_SIZE_MENU);
- }
-
out:
g_object_set (cell,
"visible", pixbuf != NULL,
EmpathyContactListView *view)
{
EmpathyContactListViewPriv *priv = GET_PRIV (view);
+ GtkTreePath *path;
+ GtkTreeViewColumn *focus_column;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean set_cursor = FALSE;
gtk_tree_model_filter_refilter (priv->filter);
+
+ /* Set cursor on the first contact. If it is already set on a group,
+ * set it on its first child contact. Note that first child of a group
+ * is its separator, that's why we actually set to the 2nd
+ */
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+ gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
+
+ if (path == NULL) {
+ path = gtk_tree_path_new_from_string ("0:1");
+ set_cursor = TRUE;
+ } else if (gtk_tree_path_get_depth (path) < 2) {
+ gboolean is_group;
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter,
+ EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
+ -1);
+
+ if (is_group) {
+ gtk_tree_path_down (path);
+ gtk_tree_path_next (path);
+ set_cursor = TRUE;
+ }
+ }
+
+ if (set_cursor) {
+ /* FIXME: Workaround for GTK bug #621651, we have to make sure
+ * the path is valid. */
+ if (gtk_tree_model_get_iter (model, &iter, path)) {
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
+ focus_column, FALSE);
+ }
+ }
+
+ gtk_tree_path_free (path);
+}
+
+static void
+contact_list_view_search_activate_cb (GtkWidget *search,
+ EmpathyContactListView *view)
+{
+ GtkTreePath *path;
+ GtkTreeViewColumn *focus_column;
+
+ gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
+ if (path != NULL) {
+ gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path,
+ focus_column);
+ gtk_tree_path_free (path);
+
+ gtk_widget_hide (search);
+ }
+}
+
+static gboolean
+contact_list_view_search_key_navigation_cb (GtkWidget *search,
+ GdkEvent *event,
+ EmpathyContactListView *view)
+{
+ GdkEvent *new_event;
+ gboolean ret = FALSE;
+
+ new_event = gdk_event_copy (event);
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+ ret = gtk_widget_event (GTK_WIDGET (view), new_event);
+ gtk_widget_grab_focus (search);
+
+ gdk_event_free (new_event);
+
+ return ret;
}
static void
g_free (name);
}
+static void
+contact_list_view_verify_group_visibility (EmpathyContactListView *view,
+ GtkTreePath *path)
+{
+ EmpathyContactListViewPriv *priv = GET_PRIV (view);
+ GtkTreeModel *model;
+ GtkTreePath *parent_path;
+ GtkTreeIter parent_iter;
+
+ if (gtk_tree_path_get_depth (path) < 2)
+ return;
+
+ /* A group row is visible if and only if at least one if its child is
+ * visible. So when a row is inserted/deleted/changed in the base model,
+ * that could modify the visibility of its parent in the filter model.
+ */
+
+ model = GTK_TREE_MODEL (priv->store);
+ parent_path = gtk_tree_path_copy (path);
+ gtk_tree_path_up (parent_path);
+ if (gtk_tree_model_get_iter (model, &parent_iter, parent_path)) {
+ /* This tells the filter to verify the visibility of that row,
+ * and show/hide it if necessary */
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
+ parent_path, &parent_iter);
+ }
+ gtk_tree_path_free (parent_path);
+}
+
+static void
+contact_list_view_store_row_changed_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ EmpathyContactListView *view)
+{
+ contact_list_view_verify_group_visibility (view, path);
+}
+
+static void
+contact_list_view_store_row_deleted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ EmpathyContactListView *view)
+{
+ contact_list_view_verify_group_visibility (view, path);
+}
+
static void
contact_list_view_constructed (GObject *object)
{
EmpathyContactListViewPriv *priv = GET_PRIV (view);
GtkCellRenderer *cell;
GtkTreeViewColumn *col;
- guint i;
-
- gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
- empathy_contact_list_store_search_equal_func,
- NULL, NULL);
priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
GTK_TREE_MODEL (priv->store), NULL));
gtk_tree_view_set_model (GTK_TREE_VIEW (view),
GTK_TREE_MODEL (priv->filter));
+ tp_g_signal_connect_object (priv->store, "row-changed",
+ G_CALLBACK (contact_list_view_store_row_changed_cb),
+ view, 0);
+ tp_g_signal_connect_object (priv->store, "row-inserted",
+ G_CALLBACK (contact_list_view_store_row_changed_cb),
+ view, 0);
+ tp_g_signal_connect_object (priv->store, "row-deleted",
+ G_CALLBACK (contact_list_view_store_row_deleted_cb),
+ view, 0);
+
/* Setup view */
/* Setting reorderable is a hack that gets us row previews as drag icons
for free. We override all the drag handlers. It's tricky to get the
/* Actually add the column now we have added all cell renderers */
gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
-
- /* Drag & Drop. */
- for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
- drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
- FALSE);
- }
-
- for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
- drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
- FALSE);
- }
}
static void
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
- _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
+ g_cclosure_marshal_generic,
G_TYPE_NONE,
3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
EmpathyContact *
empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
{
- EmpathyContactListViewPriv *priv;
GtkTreeSelection *selection;
GtkTreeIter iter;
GtkTreeModel *model;
g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
- priv = GET_PRIV (view);
-
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
return NULL;
EmpathyContactListFlags
empathy_contact_list_view_get_flags (EmpathyContactListView *view)
{
- EmpathyContactListViewPriv *priv;
GtkTreeSelection *selection;
GtkTreeIter iter;
GtkTreeModel *model;
g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
- priv = GET_PRIV (view);
-
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
return 0;
empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
gboolean *is_fake_group)
{
- EmpathyContactListViewPriv *priv;
GtkTreeSelection *selection;
GtkTreeIter iter;
GtkTreeModel *model;
g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
- priv = GET_PRIV (view);
-
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
return NULL;
parent = empathy_get_toplevel_window (GTK_WIDGET (view));
text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
- empathy_contact_get_name (contact));
+ empathy_contact_get_alias (contact));
if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
EmpathyContactList *list;
g_signal_handlers_disconnect_by_func (priv->search_widget,
contact_list_view_search_text_notify_cb,
view);
+ g_signal_handlers_disconnect_by_func (priv->search_widget,
+ contact_list_view_search_activate_cb,
+ view);
+ g_signal_handlers_disconnect_by_func (priv->search_widget,
+ contact_list_view_search_key_navigation_cb,
+ view);
g_signal_handlers_disconnect_by_func (priv->search_widget,
contact_list_view_search_hide_cb,
view);
g_signal_connect (priv->search_widget, "notify::text",
G_CALLBACK (contact_list_view_search_text_notify_cb),
view);
+ g_signal_connect (priv->search_widget, "activate",
+ G_CALLBACK (contact_list_view_search_activate_cb),
+ view);
+ g_signal_connect (priv->search_widget, "key-navigation",
+ G_CALLBACK (contact_list_view_search_key_navigation_cb),
+ view);
g_signal_connect (priv->search_widget, "hide",
G_CALLBACK (contact_list_view_search_hide_cb),
view);