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