1 /* Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
2 * Copyright (C) 2014 Charles Lehner and the Claws Mail team
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
25 #include <gdk/gdkkeysyms.h>
26 #include <glib/gi18n.h>
32 #include "ldapserver.h"
33 #include "ldapupdate.h"
36 #include "addressbook.h"
37 #include "addressitem.h"
38 #include "addrmerge.h"
39 #include "alertpanel.h"
42 #include "prefs_common.h"
60 static void addrmerge_done(struct AddrMergePage *page)
62 g_list_free(page->emails);
63 g_list_free(page->persons);
64 gtk_widget_destroy(GTK_WIDGET(page->dialog));
68 static void addrmerge_do_merge(struct AddrMergePage *page)
73 ItemPerson *target = page->target;
74 ItemPerson *nameTarget = page->nameTarget;
76 gtk_cmclist_freeze(GTK_CMCLIST(page->clist));
78 /* Update target name */
79 if (nameTarget && nameTarget != target) {
80 target->status = UPDATE_ENTRY;
81 addritem_person_set_first_name( target, nameTarget->firstName );
82 addritem_person_set_last_name( target, nameTarget->lastName );
83 addritem_person_set_nick_name( target, nameTarget->nickName );
84 addritem_person_set_common_name( target, ADDRITEM_NAME(nameTarget ));
87 /* Merge emails into target */
88 for (node = page->emails; node; node = node->next) {
90 person = ( ItemPerson * ) ADDRITEM_PARENT(email);
91 /* Remove the email from the person */
92 email = addrbook_person_remove_email( page->abf, person, email );
94 addrcache_remove_email( page->abf->addressCache, email );
95 /* Add the email to the target */
96 addrcache_person_add_email( page->abf->addressCache, target, email );
98 person->status = UPDATE_ENTRY;
99 addressbook_folder_refresh_one_person( page->clist, person );
102 /* Merge persons into target */
103 for (node = page->persons; node; node = node->next) {
104 GList *nodeE, *nodeA;
107 if (person == target) continue;
108 person->status = DELETE_ENTRY;
110 /* Move all emails to the target */
111 for (nodeE = person->listEMail; nodeE; nodeE = nodeE->next) {
113 addritem_person_add_email( target, email );
115 g_list_free( person->listEMail );
116 person->listEMail = NULL;
118 /* Move all attributes to the target */
119 for (nodeA = person->listAttrib; nodeA; nodeA = nodeA->next) {
120 UserAttribute *attrib = nodeA->data;
121 addritem_person_add_attribute( target, attrib );
123 g_list_free( person->listAttrib );
124 person->listAttrib = NULL;
126 /* Remove the person */
127 addrselect_list_remove( page->addressSelect, (AddrItemObject *)person );
128 addressbook_folder_remove_one_person( page->clist, person );
129 if (page->pobj->type == ADDR_ITEM_FOLDER)
130 addritem_folder_remove_person(ADAPTER_FOLDER(page->pobj)->itemFolder, person);
131 person = addrbook_remove_person( page->abf, person );
134 gchar *filename = addritem_person_get_picture(person);
135 if ((strcmp2(person->picture, target->picture) &&
136 filename && is_file_exist(filename)))
137 claws_unlink(filename);
140 addritem_free_item_person( person );
144 addressbook_folder_refresh_one_person( page->clist, target );
146 addrbook_set_dirty( page->abf, TRUE );
147 addressbook_export_to_file();
150 if (page->ds && page->ds->type == ADDR_IF_LDAP) {
151 LdapServer *server = page->ds->rawDataSource;
152 ldapsvr_set_modified(server, TRUE);
153 ldapsvr_update_book(server, NULL);
156 gtk_cmclist_thaw(GTK_CMCLIST(page->clist));
158 addrmerge_done(page);
161 static void addrmerge_dialog_cb(GtkWidget* widget, gint action, gpointer data) {
162 struct AddrMergePage* page = data;
164 if (action != GTK_RESPONSE_ACCEPT)
165 return addrmerge_done(page);
167 addrmerge_do_merge(page);
170 static void addrmerge_update_dialog_sensitive( struct AddrMergePage *page )
172 gboolean canMerge = (page->target && page->nameTarget);
173 gtk_dialog_set_response_sensitive( GTK_DIALOG(page->dialog),
174 GTK_RESPONSE_ACCEPT, canMerge );
177 static void addrmerge_name_selected( GtkCMCList *clist, gint row, gint column, GdkEvent *event, struct AddrMergePage *page )
179 ItemPerson *person = gtk_cmclist_get_row_data( clist, row );
180 page->nameTarget = person;
181 addrmerge_update_dialog_sensitive(page);
184 static void addrmerge_picture_selected(GtkTreeView *treeview,
185 struct AddrMergePage *page)
190 ItemPerson *pictureTarget;
192 /* Get selected picture target */
193 model = gtk_icon_view_get_model(GTK_ICON_VIEW(page->iconView));
194 list = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(page->iconView));
197 if (gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)list->data)) {
198 gtk_tree_model_get(model, &iter,
199 SET_PERSON, &pictureTarget,
201 page->target = pictureTarget;
204 gtk_tree_path_free(list->data);
207 addrmerge_update_dialog_sensitive(page);
210 static void addrmerge_prompt( struct AddrMergePage *page )
214 GtkWidget *mvbox, *vbox, *hbox;
216 GtkWidget *iconView = NULL;
217 GtkWidget *namesList = NULL;
218 MainWindow *mainwin = mainwindow_get_mainwindow();
219 GtkListStore *store = NULL;
223 GError *error = NULL;
224 gchar *msg, *label_msg;
226 dialog = page->dialog = gtk_dialog_new_with_buttons (
227 _("Merge addresses"),
228 GTK_WINDOW(mainwin->window),
229 GTK_DIALOG_DESTROY_WITH_PARENT,
236 g_signal_connect ( dialog, "response",
237 G_CALLBACK(addrmerge_dialog_cb), page);
239 mvbox = gtk_vbox_new(FALSE, 4);
240 gtk_container_add(GTK_CONTAINER(
241 gtk_dialog_get_content_area(GTK_DIALOG(dialog))), mvbox);
242 gtk_container_set_border_width(GTK_CONTAINER(mvbox), 8);
243 hbox = gtk_hbox_new(FALSE, 4);
244 gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
245 gtk_box_pack_start(GTK_BOX(mvbox),
246 hbox, FALSE, FALSE, 0);
248 msg = page->pickPicture || page->pickName ?
249 _("Merging %u contacts." ) :
250 _("Really merge these %u contacts?" );
251 label_msg = g_strdup_printf(msg,
252 g_list_length(page->addressSelect->listSelect));
253 label = gtk_label_new( label_msg );
254 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
255 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
256 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
259 if (page->pickPicture) {
260 GtkWidget *scrollwinPictures;
262 store = gtk_list_store_new(N_SET_COLUMNS,
266 gtk_list_store_clear(store);
268 vbox = gtkut_get_options_frame(mvbox, &frame,
269 _("Keep which picture?"));
270 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
272 scrollwinPictures = gtk_scrolled_window_new(NULL, NULL);
273 gtk_container_set_border_width(GTK_CONTAINER(scrollwinPictures), 1);
274 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinPictures),
275 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
276 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinPictures),
278 gtk_box_pack_start (GTK_BOX (vbox), scrollwinPictures, FALSE, FALSE, 0);
279 gtk_widget_set_size_request(scrollwinPictures, 464, 192);
281 iconView = gtk_icon_view_new_with_model(GTK_TREE_MODEL(store));
282 gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(iconView), GTK_SELECTION_SINGLE);
283 gtk_icon_view_set_pixbuf_column(GTK_ICON_VIEW(iconView), SET_ICON);
284 gtk_container_add(GTK_CONTAINER(scrollwinPictures), GTK_WIDGET(iconView));
285 g_signal_connect(G_OBJECT(iconView), "selection-changed",
286 G_CALLBACK(addrmerge_picture_selected), page);
288 /* Add pictures from persons */
289 for (node = page->persons; node; node = node->next) {
292 filename = addritem_person_get_picture(person);
293 if (filename && is_file_exist(filename)) {
297 pixbuf = gdk_pixbuf_new_from_file(filename, &error);
299 debug_print("Failed to read image: \n%s",
305 image = gtk_image_new();
306 gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
308 gtk_list_store_append(store, &iter);
309 gtk_list_store_set(store, &iter,
319 if (page->pickName) {
320 GtkWidget *scrollwinNames;
321 gchar *name_titles[N_NAME_COLS];
323 name_titles[COL_DISPLAYNAME] = _("Display Name");
324 name_titles[COL_FIRSTNAME] = _("First Name");
325 name_titles[COL_LASTNAME] = _("Last Name");
326 name_titles[COL_NICKNAME] = _("Nickname");
328 store = gtk_list_store_new(N_SET_COLUMNS,
332 gtk_list_store_clear(store);
334 vbox = gtkut_get_options_frame(mvbox, &frame,
335 _("Keep which name?"));
336 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
338 scrollwinNames = gtk_scrolled_window_new(NULL, NULL);
339 gtk_container_set_border_width(GTK_CONTAINER(scrollwinNames), 1);
340 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinNames),
341 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
342 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinNames),
344 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scrollwinNames), FALSE, FALSE, 0);
346 namesList = gtk_cmclist_new_with_titles(N_NAME_COLS, name_titles);
347 gtk_widget_set_can_focus(GTK_CMCLIST(namesList)->column[0].button, FALSE);
348 gtk_cmclist_set_selection_mode(GTK_CMCLIST(namesList), GTK_SELECTION_BROWSE);
349 gtk_cmclist_set_column_width(GTK_CMCLIST(namesList), COL_DISPLAYNAME, 164);
351 gtk_container_add(GTK_CONTAINER(scrollwinNames), namesList);
353 /* Add names from persons */
354 for (node = page->persons; node; node = node->next) {
357 gchar *text[N_NAME_COLS];
358 text[COL_DISPLAYNAME] = ADDRITEM_NAME(person);
359 text[COL_FIRSTNAME] = person->firstName;
360 text[COL_LASTNAME] = person->lastName;
361 text[COL_NICKNAME] = person->nickName;
362 row = gtk_cmclist_insert( GTK_CMCLIST(namesList), -1, text );
363 gtk_cmclist_set_row_data( GTK_CMCLIST(namesList), row, person );
366 g_signal_connect(G_OBJECT(namesList), "select_row",
367 G_CALLBACK(addrmerge_name_selected), page);
370 page->iconView = iconView;
371 page->namesList = namesList;
373 addrmerge_update_dialog_sensitive(page);
374 gtk_widget_show_all(dialog);
377 void addrmerge_merge(
380 AddressDataSource *ds,
381 AddrSelectList *list)
383 struct AddrMergePage* page;
384 AdapterDSource *ads = NULL;
385 AddressBookFile *abf;
388 AddrSelectItem *item;
390 ItemPerson *person, *target = NULL, *nameTarget = NULL;
391 GList *persons = NULL, *emails = NULL;
392 gboolean pickPicture = FALSE, pickName = FALSE;
394 /* Test for read only */
395 if( ds->interface->readOnly ) {
396 alertpanel( _("Merge addresses"),
397 _("This address data is readonly and cannot be deleted."),
398 GTK_STOCK_CLOSE, NULL, NULL, ALERTFOCUS_FIRST );
402 /* Test whether Ok to proceed */
404 if( pobj->type == ADDR_DATASOURCE ) {
405 ads = ADAPTER_DSOURCE(pobj);
406 if( ads->subType == ADDR_BOOK ) procFlag = TRUE;
408 else if( pobj->type == ADDR_ITEM_FOLDER ) {
411 else if( pobj->type == ADDR_ITEM_GROUP ) {
414 if( ! procFlag ) return;
415 abf = ds->rawDataSource;
416 if( abf == NULL ) return;
418 /* Gather selected persons and emails */
419 for (node = list->listSelect; node; node = node->next) {
421 aio = ( AddrItemObject * ) item->addressItem;
422 if( aio->type == ITEMTYPE_EMAIL ) {
423 emails = g_list_prepend(emails, aio);
424 } else if( aio->type == ITEMTYPE_PERSON ) {
425 persons = g_list_prepend(persons, aio);
429 /* Check if more than one person has a picture */
430 for (node = persons; node; node = node->next) {
433 filename = addritem_person_get_picture(person);
434 if (filename && is_file_exist(filename)) {
435 if (target == NULL) {
446 if (pickPicture || target) {
447 /* At least one person had a picture */
448 } else if (persons && persons->data) {
449 /* No person had a picture. Use the first person as target */
450 target = persons->data;
452 /* No persons in list. Abort */
456 /* Pick which name to keep */
457 for (node = persons; node; node = node->next) {
459 if (nameTarget == NULL) {
461 } else if (nameTarget == person) {
463 } else if (strcmp2(person->firstName, nameTarget->firstName) ||
464 strcmp2(person->lastName, nameTarget->lastName) ||
465 strcmp2(person->nickName, nameTarget->nickName) ||
466 strcmp2(ADDRITEM_NAME(person), ADDRITEM_NAME(nameTarget))) {
472 /* No persons in list */
477 page = g_new0(struct AddrMergePage, 1);
478 page->pickPicture = pickPicture;
479 page->pickName = pickName;
480 page->target = target;
481 page->nameTarget = nameTarget;
482 page->addressSelect = list;
483 page->persons = persons;
484 page->emails = emails;
490 addrmerge_prompt(page);
494 g_list_free( emails );
495 g_list_free( persons );