1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* e-addressbook-selector.c
3  *
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "evolution-config.h"
20 
21 #include "e-addressbook-selector.h"
22 
23 #include <e-util/e-util.h>
24 
25 #include "util/eab-book-util.h"
26 #include "eab-contact-merging.h"
27 
28 #define E_ADDRESSBOOK_SELECTOR_GET_PRIVATE(obj) \
29 	(G_TYPE_INSTANCE_GET_PRIVATE \
30 	((obj), E_TYPE_ADDRESSBOOK_SELECTOR, EAddressbookSelectorPrivate))
31 
32 typedef struct _MergeContext MergeContext;
33 
34 struct _EAddressbookSelectorPrivate {
35 	EAddressbookView *current_view;
36 };
37 
38 struct _MergeContext {
39 	ESourceRegistry *registry;
40 	EBookClient *source_client;
41 	EBookClient *target_client;
42 
43 	EContact *current_contact;
44 	GSList *remaining_contacts;
45 	guint pending_removals;
46 	gboolean pending_adds;
47 
48 	guint remove_from_source : 1;
49 	guint copy_done : 1;
50 };
51 
52 enum {
53 	PROP_0,
54 	PROP_CURRENT_VIEW
55 };
56 
57 static GtkTargetEntry drag_types[] = {
58 	{ (gchar *) "text/x-source-vcard", 0, 1 }
59 };
60 
G_DEFINE_TYPE(EAddressbookSelector,e_addressbook_selector,E_TYPE_CLIENT_SELECTOR)61 G_DEFINE_TYPE (
62 	EAddressbookSelector,
63 	e_addressbook_selector,
64 	E_TYPE_CLIENT_SELECTOR)
65 
66 static void
67 merge_context_next (MergeContext *merge_context)
68 {
69 	GSList *list;
70 
71 	merge_context->current_contact = NULL;
72 	if (!merge_context->remaining_contacts)
73 		return;
74 
75 	list = merge_context->remaining_contacts;
76 	merge_context->current_contact = list->data;
77 	list = g_slist_delete_link (list, list);
78 	merge_context->remaining_contacts = list;
79 }
80 
81 static MergeContext *
merge_context_new(ESourceRegistry * registry,EBookClient * source_client,EBookClient * target_client,GSList * contact_list)82 merge_context_new (ESourceRegistry *registry,
83                    EBookClient *source_client,
84                    EBookClient *target_client,
85                    GSList *contact_list)
86 {
87 	MergeContext *merge_context;
88 
89 	merge_context = g_slice_new0 (MergeContext);
90 	merge_context->registry = g_object_ref (registry);
91 	merge_context->source_client = source_client;
92 	merge_context->target_client = target_client;
93 	merge_context->remaining_contacts = contact_list;
94 	merge_context_next (merge_context);
95 
96 	return merge_context;
97 }
98 
99 static void
merge_context_free(MergeContext * merge_context)100 merge_context_free (MergeContext *merge_context)
101 {
102 	if (merge_context->registry != NULL)
103 		g_object_unref (merge_context->registry);
104 
105 	if (merge_context->source_client != NULL)
106 		g_object_unref (merge_context->source_client);
107 
108 	if (merge_context->target_client != NULL)
109 		g_object_unref (merge_context->target_client);
110 
111 	g_slice_free (MergeContext, merge_context);
112 }
113 
114 static void
addressbook_selector_removed_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)115 addressbook_selector_removed_cb (GObject *source_object,
116                                  GAsyncResult *result,
117                                  gpointer user_data)
118 {
119 	EBookClient *book_client = E_BOOK_CLIENT (source_object);
120 	MergeContext *merge_context = user_data;
121 	GError *error = NULL;
122 
123 	e_book_client_remove_contact_finish (book_client, result, &error);
124 
125 	if (error != NULL) {
126 		g_warning (
127 			"%s: Failed to remove contact: %s",
128 			G_STRFUNC, error->message);
129 		g_error_free (error);
130 	}
131 
132 	merge_context->pending_removals--;
133 
134 	if (merge_context->pending_adds)
135 		return;
136 
137 	if (merge_context->pending_removals > 0)
138 		return;
139 
140 	merge_context_free (merge_context);
141 }
142 
143 static void
addressbook_selector_merge_next_cb(EBookClient * book_client,const GError * error,const gchar * id,gpointer closure)144 addressbook_selector_merge_next_cb (EBookClient *book_client,
145                                     const GError *error,
146                                     const gchar *id,
147                                     gpointer closure)
148 {
149 	MergeContext *merge_context = closure;
150 
151 	if (merge_context->remove_from_source && !error) {
152 		/* Remove previous contact from source. */
153 		e_book_client_remove_contact (
154 			merge_context->source_client,
155 			merge_context->current_contact,
156 			E_BOOK_OPERATION_FLAG_NONE, NULL,
157 			addressbook_selector_removed_cb, merge_context);
158 		merge_context->pending_removals++;
159 	}
160 
161 	g_object_unref (merge_context->current_contact);
162 
163 	if (merge_context->remaining_contacts != NULL) {
164 		merge_context_next (merge_context);
165 		eab_merging_book_add_contact (
166 			merge_context->registry,
167 			merge_context->target_client,
168 			merge_context->current_contact,
169 			addressbook_selector_merge_next_cb, merge_context);
170 
171 	} else if (merge_context->pending_removals == 0) {
172 		merge_context_free (merge_context);
173 	} else
174 		merge_context->pending_adds = FALSE;
175 }
176 
177 static void
addressbook_selector_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)178 addressbook_selector_set_property (GObject *object,
179                                    guint property_id,
180                                    const GValue *value,
181                                    GParamSpec *pspec)
182 {
183 	switch (property_id) {
184 		case PROP_CURRENT_VIEW:
185 			e_addressbook_selector_set_current_view (
186 				E_ADDRESSBOOK_SELECTOR (object),
187 				g_value_get_object (value));
188 			return;
189 	}
190 
191 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
192 }
193 
194 static void
addressbook_selector_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)195 addressbook_selector_get_property (GObject *object,
196                                    guint property_id,
197                                    GValue *value,
198                                    GParamSpec *pspec)
199 {
200 	switch (property_id) {
201 		case PROP_CURRENT_VIEW:
202 			g_value_set_object (
203 				value,
204 				e_addressbook_selector_get_current_view (
205 				E_ADDRESSBOOK_SELECTOR (object)));
206 			return;
207 	}
208 
209 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
210 }
211 
212 static void
addressbook_selector_dispose(GObject * object)213 addressbook_selector_dispose (GObject *object)
214 {
215 	EAddressbookSelectorPrivate *priv;
216 
217 	priv = E_ADDRESSBOOK_SELECTOR_GET_PRIVATE (object);
218 	g_clear_object (&priv->current_view);
219 
220 	/* Chain up to parent's dispose() method. */
221 	G_OBJECT_CLASS (e_addressbook_selector_parent_class)->dispose (object);
222 }
223 
224 static void
addressbook_selector_constructed(GObject * object)225 addressbook_selector_constructed (GObject *object)
226 {
227 	ESourceSelector *selector;
228 	ESourceRegistry *registry;
229 	ESource *source;
230 
231 	selector = E_SOURCE_SELECTOR (object);
232 	registry = e_source_selector_get_registry (selector);
233 	source = e_source_registry_ref_default_address_book (registry);
234 	e_source_selector_set_primary_selection (selector, source);
235 	g_object_unref (source);
236 
237 	/* Chain up to parent's constructed() method. */
238 	G_OBJECT_CLASS (e_addressbook_selector_parent_class)->constructed (object);
239 }
240 
241 static void
target_client_connect_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)242 target_client_connect_cb (GObject *source_object,
243                           GAsyncResult *result,
244                           gpointer user_data)
245 {
246 	MergeContext *merge_context = user_data;
247 	EClient *client;
248 	GError *error = NULL;
249 
250 	g_return_if_fail (merge_context != NULL);
251 
252 	client = e_client_selector_get_client_finish (
253 		E_CLIENT_SELECTOR (source_object), result, &error);
254 
255 	/* Sanity check. */
256 	g_return_if_fail (
257 		((client != NULL) && (error == NULL)) ||
258 		((client == NULL) && (error != NULL)));
259 
260 	if (error != NULL) {
261 		g_warning ("%s: %s", G_STRFUNC, error->message);
262 		g_error_free (error);
263 	}
264 
265 	merge_context->target_client = client ? E_BOOK_CLIENT (client) : NULL;
266 
267 	if (!merge_context->target_client) {
268 		g_slist_foreach (
269 			merge_context->remaining_contacts,
270 			(GFunc) g_object_unref, NULL);
271 		g_slist_free (merge_context->remaining_contacts);
272 
273 		merge_context_free (merge_context);
274 		return;
275 	}
276 
277 	eab_merging_book_add_contact (
278 		merge_context->registry,
279 		merge_context->target_client,
280 		merge_context->current_contact,
281 		addressbook_selector_merge_next_cb, merge_context);
282 }
283 
284 static gboolean
addressbook_selector_data_dropped(ESourceSelector * selector,GtkSelectionData * selection_data,ESource * destination,GdkDragAction action,guint info)285 addressbook_selector_data_dropped (ESourceSelector *selector,
286                                    GtkSelectionData *selection_data,
287                                    ESource *destination,
288                                    GdkDragAction action,
289                                    guint info)
290 {
291 	EAddressbookSelectorPrivate *priv;
292 	MergeContext *merge_context;
293 	EAddressbookModel *model;
294 	EBookClient *source_client;
295 	ESource *source_source = NULL;
296 	ESourceRegistry *registry;
297 	GSList *list;
298 	const gchar *string;
299 	gboolean remove_from_source;
300 
301 	priv = E_ADDRESSBOOK_SELECTOR_GET_PRIVATE (selector);
302 	g_return_val_if_fail (priv->current_view != NULL, FALSE);
303 
304 	string = (const gchar *) gtk_selection_data_get_data (selection_data);
305 	remove_from_source = (action == GDK_ACTION_MOVE);
306 
307 	registry = e_source_selector_get_registry (selector);
308 
309 	if (info == drag_types[0].info)
310 		eab_source_and_contact_list_from_string (
311 			registry, string, &source_source, &list);
312 	else
313 		list = eab_contact_list_from_string (string);
314 
315 	if (list == NULL) {
316 		g_clear_object (&source_source);
317 		return FALSE;
318 	}
319 
320 	model = e_addressbook_view_get_model (priv->current_view);
321 	source_client = e_addressbook_model_get_client (model);
322 	g_return_val_if_fail (E_IS_BOOK_CLIENT (source_client), FALSE);
323 
324 	if (remove_from_source && source_source &&
325 	    !e_source_equal (source_source, e_client_get_source (E_CLIENT (source_client)))) {
326 		g_warning ("%s: Source book '%s' doesn't match the view client '%s', skipping drop",
327 			G_STRFUNC, e_source_get_uid (source_source),
328 			e_source_get_uid (e_client_get_source (E_CLIENT (source_client))));
329 		g_clear_object (&source_source);
330 		return FALSE;
331 	}
332 
333 	g_clear_object (&source_source);
334 
335 	merge_context = merge_context_new (
336 		registry, g_object_ref (source_client), NULL, list);
337 	merge_context->remove_from_source = remove_from_source;
338 	merge_context->pending_adds = TRUE;
339 
340 	e_client_selector_get_client (
341 		E_CLIENT_SELECTOR (selector), destination, FALSE, 30, NULL,
342 		target_client_connect_cb, merge_context);
343 
344 	return TRUE;
345 }
346 
347 static void
e_addressbook_selector_class_init(EAddressbookSelectorClass * class)348 e_addressbook_selector_class_init (EAddressbookSelectorClass *class)
349 {
350 	GObjectClass *object_class;
351 	ESourceSelectorClass *selector_class;
352 
353 	g_type_class_add_private (class, sizeof (EAddressbookSelectorPrivate));
354 
355 	object_class = G_OBJECT_CLASS (class);
356 	object_class->set_property = addressbook_selector_set_property;
357 	object_class->get_property = addressbook_selector_get_property;
358 	object_class->dispose = addressbook_selector_dispose;
359 	object_class->constructed = addressbook_selector_constructed;
360 
361 	selector_class = E_SOURCE_SELECTOR_CLASS (class);
362 	selector_class->data_dropped = addressbook_selector_data_dropped;
363 
364 	g_object_class_install_property (
365 		object_class,
366 		PROP_CURRENT_VIEW,
367 		g_param_spec_object (
368 			"current-view",
369 			NULL,
370 			NULL,
371 			E_TYPE_ADDRESSBOOK_VIEW,
372 			G_PARAM_READWRITE));
373 }
374 
375 static void
e_addressbook_selector_init(EAddressbookSelector * selector)376 e_addressbook_selector_init (EAddressbookSelector *selector)
377 {
378 	selector->priv = E_ADDRESSBOOK_SELECTOR_GET_PRIVATE (selector);
379 
380 	e_source_selector_set_show_colors (
381 		E_SOURCE_SELECTOR (selector), FALSE);
382 
383 	e_source_selector_set_show_toggles (
384 		E_SOURCE_SELECTOR (selector), FALSE);
385 
386 	gtk_drag_dest_set (
387 		GTK_WIDGET (selector), GTK_DEST_DEFAULT_ALL,
388 		drag_types, G_N_ELEMENTS (drag_types),
389 		GDK_ACTION_COPY | GDK_ACTION_MOVE);
390 
391 	e_drag_dest_add_directory_targets (GTK_WIDGET (selector));
392 }
393 
394 GtkWidget *
e_addressbook_selector_new(EClientCache * client_cache)395 e_addressbook_selector_new (EClientCache *client_cache)
396 {
397 	ESourceRegistry *registry;
398 	GtkWidget *widget;
399 
400 	g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
401 
402 	registry = e_client_cache_ref_registry (client_cache);
403 
404 	widget = g_object_new (
405 		E_TYPE_ADDRESSBOOK_SELECTOR,
406 		"client-cache", client_cache,
407 		"extension-name", E_SOURCE_EXTENSION_ADDRESS_BOOK,
408 		"registry", registry, NULL);
409 
410 	g_object_unref (registry);
411 
412 	return widget;
413 }
414 
415 EAddressbookView *
e_addressbook_selector_get_current_view(EAddressbookSelector * selector)416 e_addressbook_selector_get_current_view (EAddressbookSelector *selector)
417 {
418 	g_return_val_if_fail (E_IS_ADDRESSBOOK_SELECTOR (selector), NULL);
419 
420 	return selector->priv->current_view;
421 }
422 
423 void
e_addressbook_selector_set_current_view(EAddressbookSelector * selector,EAddressbookView * current_view)424 e_addressbook_selector_set_current_view (EAddressbookSelector *selector,
425                                          EAddressbookView *current_view)
426 {
427 	/* XXX This is only needed for moving contacts via drag-and-drop.
428 	 *     The selection data doesn't include the source of the data
429 	 *     (the model for the currently selected address book view),
430 	 *     so we have to rely on it being provided to us.  I would
431 	 *     be happy to see this function go away. */
432 
433 	g_return_if_fail (E_IS_ADDRESSBOOK_SELECTOR (selector));
434 
435 	if (current_view != NULL)
436 		g_return_if_fail (E_IS_ADDRESSBOOK_VIEW (current_view));
437 
438 	if (selector->priv->current_view == current_view)
439 		return;
440 
441 	g_clear_object (&selector->priv->current_view);
442 
443 	if (current_view != NULL)
444 		g_object_ref (current_view);
445 
446 	selector->priv->current_view = current_view;
447 
448 	g_object_notify (G_OBJECT (selector), "current-view");
449 }
450