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