fix bug 4773, 'remove obsolescent AC_C_CONST'
[claws.git] / src / addrmerge.c
blob3258e04a855e7a0f9237a78ecb6c66ee46f4588f
1 /* Claws Mail -- a GTK based, lightweight, and fast e-mail client
2 * Copyright (C) 2014-2023 the Claws Mail team and Charles Lehner
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/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include <gdk/gdk.h>
25 #include <gdk/gdkkeysyms.h>
26 #include <glib/gi18n.h>
27 #include <string.h>
29 #include "defs.h"
31 #ifdef USE_LDAP
32 #include "ldapserver.h"
33 #include "ldapupdate.h"
34 #endif
35 #include "addrbook.h"
36 #include "addressbook.h"
37 #include "addressitem.h"
38 #include "addrmerge.h"
39 #include "alertpanel.h"
40 #include "gtkutils.h"
41 #include "file-utils.h"
42 #include "utils.h"
43 #include "prefs_common.h"
45 enum
47 COL_DISPLAYNAME,
48 COL_FIRSTNAME,
49 COL_LASTNAME,
50 COL_NICKNAME,
51 N_NAME_COLS
54 enum
56 SET_ICON,
57 SET_PERSON,
58 N_SET_COLUMNS
61 static void addrmerge_done(struct AddrMergePage *page)
63 g_list_free(page->emails);
64 g_list_free(page->persons);
65 gtk_widget_destroy(GTK_WIDGET(page->dialog));
66 g_free(page);
69 static void addrmerge_do_merge(struct AddrMergePage *page)
71 GList *node;
72 ItemEMail *email;
73 ItemPerson *person;
74 ItemPerson *target = page->target;
75 ItemPerson *nameTarget = page->nameTarget;
77 gtk_cmclist_freeze(GTK_CMCLIST(page->clist));
79 /* Update target name */
80 if (nameTarget && nameTarget != target) {
81 target->status = UPDATE_ENTRY;
82 addritem_person_set_first_name( target, nameTarget->firstName );
83 addritem_person_set_last_name( target, nameTarget->lastName );
84 addritem_person_set_nick_name( target, nameTarget->nickName );
85 addritem_person_set_common_name( target, ADDRITEM_NAME(nameTarget ));
88 /* Merge emails into target */
89 for (node = page->emails; node; node = node->next) {
90 email = node->data;
91 person = ( ItemPerson * ) ADDRITEM_PARENT(email);
92 /* Remove the email from the person */
93 email = addrbook_person_remove_email( page->abf, person, email );
94 if( email ) {
95 addrcache_remove_email( page->abf->addressCache, email );
96 /* Add the email to the target */
97 addrcache_person_add_email( page->abf->addressCache, target, email );
99 person->status = UPDATE_ENTRY;
100 addressbook_folder_refresh_one_person( page->clist, person );
103 /* Merge persons into target */
104 for (node = page->persons; node; node = node->next) {
105 GList *nodeE, *nodeA;
106 person = node->data;
108 if (person == target) continue;
109 person->status = DELETE_ENTRY;
111 /* Move all emails to the target */
112 for (nodeE = person->listEMail; nodeE; nodeE = nodeE->next) {
113 email = nodeE->data;
114 addritem_person_add_email( target, email );
116 g_list_free( person->listEMail );
117 person->listEMail = NULL;
119 /* Move all attributes to the target */
120 for (nodeA = person->listAttrib; nodeA; nodeA = nodeA->next) {
121 UserAttribute *attrib = nodeA->data;
122 addritem_person_add_attribute( target, attrib );
124 g_list_free( person->listAttrib );
125 person->listAttrib = NULL;
127 /* Remove the person */
128 addrselect_list_remove( page->addressSelect, (AddrItemObject *)person );
129 addressbook_folder_remove_one_person( page->clist, person );
130 if (page->pobj->type == ADDR_ITEM_FOLDER)
131 addritem_folder_remove_person(ADAPTER_FOLDER(page->pobj)->itemFolder, person);
132 person = addrbook_remove_person( page->abf, person );
134 if( person ) {
135 gchar *filename = addritem_person_get_picture(person);
136 if ((g_strcmp0(person->picture, target->picture) &&
137 filename && is_file_exist(filename)))
138 claws_unlink(filename);
139 if (filename)
140 g_free(filename);
141 addritem_free_item_person( person );
145 addressbook_folder_refresh_one_person( page->clist, target );
147 addrbook_set_dirty( page->abf, TRUE );
148 addressbook_export_to_file();
150 #ifdef USE_LDAP
151 if (page->ds && page->ds->type == ADDR_IF_LDAP) {
152 LdapServer *server = page->ds->rawDataSource;
153 ldapsvr_set_modified(server, TRUE);
154 ldapsvr_update_book(server, NULL);
156 #endif
157 gtk_cmclist_thaw(GTK_CMCLIST(page->clist));
159 addrmerge_done(page);
162 static void addrmerge_dialog_cb(GtkWidget* widget, gint action, gpointer data) {
163 struct AddrMergePage* page = data;
165 if (action != GTK_RESPONSE_ACCEPT)
166 return addrmerge_done(page);
168 addrmerge_do_merge(page);
171 static void addrmerge_update_dialog_sensitive( struct AddrMergePage *page )
173 gboolean canMerge = (page->target && page->nameTarget);
174 gtk_dialog_set_response_sensitive( GTK_DIALOG(page->dialog),
175 GTK_RESPONSE_ACCEPT, canMerge );
178 static void addrmerge_name_selected( GtkCMCList *clist, gint row, gint column, GdkEvent *event, struct AddrMergePage *page )
180 ItemPerson *person = gtk_cmclist_get_row_data( clist, row );
181 page->nameTarget = person;
182 addrmerge_update_dialog_sensitive(page);
185 static void addrmerge_picture_selected(GtkTreeView *treeview,
186 struct AddrMergePage *page)
188 GtkTreeModel *model;
189 GtkTreeIter iter;
190 GList *list;
191 ItemPerson *pictureTarget;
193 /* Get selected picture target */
194 model = gtk_icon_view_get_model(GTK_ICON_VIEW(page->iconView));
195 list = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(page->iconView));
196 page->target = NULL;
197 if (list != NULL) {
198 if (gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)list->data)) {
199 gtk_tree_model_get(model, &iter,
200 SET_PERSON, &pictureTarget,
201 -1);
202 page->target = pictureTarget;
205 gtk_tree_path_free(list->data);
206 g_list_free(list);
208 addrmerge_update_dialog_sensitive(page);
211 static void addrmerge_prompt( struct AddrMergePage *page )
213 GtkWidget *dialog;
214 GtkWidget *frame;
215 GtkWidget *mvbox, *vbox, *hbox;
216 GtkWidget *label;
217 GtkWidget *iconView = NULL;
218 GtkWidget *namesList = NULL;
219 MainWindow *mainwin = mainwindow_get_mainwindow();
220 GtkListStore *store = NULL;
221 GtkTreeIter iter;
222 GList *node;
223 ItemPerson *person;
224 GError *error = NULL;
225 gchar *msg, *label_msg;
227 dialog = page->dialog = gtk_dialog_new_with_buttons (
228 _("Merge addresses"),
229 GTK_WINDOW(mainwin->window),
230 GTK_DIALOG_DESTROY_WITH_PARENT,
231 _("_Cancel"),
232 GTK_RESPONSE_CANCEL,
233 _("_Merge"),
234 GTK_RESPONSE_ACCEPT,
235 NULL);
237 g_signal_connect ( dialog, "response",
238 G_CALLBACK(addrmerge_dialog_cb), page);
240 mvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
241 gtk_container_add(GTK_CONTAINER(
242 gtk_dialog_get_content_area(GTK_DIALOG(dialog))), mvbox);
243 gtk_container_set_border_width(GTK_CONTAINER(mvbox), 8);
244 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
245 gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
246 gtk_box_pack_start(GTK_BOX(mvbox),
247 hbox, FALSE, FALSE, 0);
249 msg = page->pickPicture || page->pickName ?
250 _("Merging %u contacts." ) :
251 _("Really merge these %u contacts?" );
252 label_msg = g_strdup_printf(msg,
253 g_list_length(page->addressSelect->listSelect));
254 label = gtk_label_new( label_msg );
255 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
256 gtk_label_set_xalign(GTK_LABEL(label),0);
257 gtk_label_set_yalign(GTK_LABEL(label),0.5);
258 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
259 g_free(label_msg);
261 if (page->pickPicture) {
262 GtkWidget *scrollwinPictures;
264 store = gtk_list_store_new(N_SET_COLUMNS,
265 GDK_TYPE_PIXBUF,
266 G_TYPE_POINTER,
267 -1);
268 gtk_list_store_clear(store);
270 vbox = gtkut_get_options_frame(mvbox, &frame,
271 _("Keep which picture?"));
272 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
274 scrollwinPictures = gtk_scrolled_window_new(NULL, NULL);
275 gtk_container_set_border_width(GTK_CONTAINER(scrollwinPictures), 1);
276 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinPictures),
277 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
278 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinPictures),
279 GTK_SHADOW_IN);
280 gtk_box_pack_start (GTK_BOX (vbox), scrollwinPictures, FALSE, FALSE, 0);
281 gtk_widget_set_size_request(scrollwinPictures, 464, 192);
283 iconView = gtk_icon_view_new_with_model(GTK_TREE_MODEL(store));
284 gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(iconView), GTK_SELECTION_SINGLE);
285 gtk_icon_view_set_pixbuf_column(GTK_ICON_VIEW(iconView), SET_ICON);
286 gtk_container_add(GTK_CONTAINER(scrollwinPictures), GTK_WIDGET(iconView));
287 g_signal_connect(G_OBJECT(iconView), "selection-changed",
288 G_CALLBACK(addrmerge_picture_selected), page);
290 /* Add pictures from persons */
291 for (node = page->persons; node; node = node->next) {
292 gchar *filename;
293 person = node->data;
294 filename = addritem_person_get_picture(person);
295 if (filename && is_file_exist(filename)) {
296 GdkPixbuf *pixbuf;
297 GtkWidget *image;
299 pixbuf = gdk_pixbuf_new_from_file(filename, &error);
300 if (error) {
301 debug_print("Failed to read image: \n%s",
302 error->message);
303 g_error_free(error);
304 continue;
307 image = gtk_image_new();
308 gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
310 gtk_list_store_append(store, &iter);
311 gtk_list_store_set(store, &iter,
312 SET_ICON, pixbuf,
313 SET_PERSON, person,
314 -1);
316 if (filename)
317 g_free(filename);
321 if (page->pickName) {
322 GtkWidget *scrollwinNames;
323 gchar *name_titles[N_NAME_COLS];
325 name_titles[COL_DISPLAYNAME] = _("Display Name");
326 name_titles[COL_FIRSTNAME] = _("First Name");
327 name_titles[COL_LASTNAME] = _("Last Name");
328 name_titles[COL_NICKNAME] = _("Nickname");
330 store = gtk_list_store_new(N_SET_COLUMNS,
331 GDK_TYPE_PIXBUF,
332 G_TYPE_POINTER,
333 -1);
334 gtk_list_store_clear(store);
336 vbox = gtkut_get_options_frame(mvbox, &frame,
337 _("Keep which name?"));
338 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
340 scrollwinNames = gtk_scrolled_window_new(NULL, NULL);
341 gtk_container_set_border_width(GTK_CONTAINER(scrollwinNames), 1);
342 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinNames),
343 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
344 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinNames),
345 GTK_SHADOW_IN);
346 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scrollwinNames), FALSE, FALSE, 0);
348 namesList = gtk_cmclist_new_with_titles(N_NAME_COLS, name_titles);
349 gtk_widget_set_can_focus(GTK_CMCLIST(namesList)->column[0].button, FALSE);
350 gtk_cmclist_set_selection_mode(GTK_CMCLIST(namesList), GTK_SELECTION_BROWSE);
351 gtk_cmclist_set_column_width(GTK_CMCLIST(namesList), COL_DISPLAYNAME, 164);
353 gtk_container_add(GTK_CONTAINER(scrollwinNames), namesList);
355 /* Add names from persons */
356 for (node = page->persons; node; node = node->next) {
357 int row;
358 person = node->data;
359 gchar *text[N_NAME_COLS];
360 text[COL_DISPLAYNAME] = ADDRITEM_NAME(person);
361 text[COL_FIRSTNAME] = person->firstName;
362 text[COL_LASTNAME] = person->lastName;
363 text[COL_NICKNAME] = person->nickName;
364 row = gtk_cmclist_insert( GTK_CMCLIST(namesList), -1, text );
365 gtk_cmclist_set_row_data( GTK_CMCLIST(namesList), row, person );
368 g_signal_connect(G_OBJECT(namesList), "select_row",
369 G_CALLBACK(addrmerge_name_selected), page);
372 page->iconView = iconView;
373 page->namesList = namesList;
375 addrmerge_update_dialog_sensitive(page);
376 gtk_widget_show_all(dialog);
379 void addrmerge_merge(
380 GtkCMCTree *clist,
381 AddressObject *pobj,
382 AddressDataSource *ds,
383 AddrSelectList *list)
385 struct AddrMergePage* page;
386 AdapterDSource *ads = NULL;
387 AddressBookFile *abf;
388 gboolean procFlag;
389 GList *node;
390 AddrSelectItem *item;
391 AddrItemObject *aio;
392 ItemPerson *person, *target = NULL, *nameTarget = NULL;
393 GList *persons = NULL, *emails = NULL;
394 gboolean pickPicture = FALSE, pickName = FALSE;
396 /* Test for read only */
397 if( ds->interface->readOnly ) {
398 alertpanel_warning(_("This address data is read-only and cannot be deleted."));
399 return;
402 /* Test whether Ok to proceed */
403 procFlag = FALSE;
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 ) {
409 procFlag = TRUE;
411 else if( pobj->type == ADDR_ITEM_GROUP ) {
412 procFlag = TRUE;
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) {
420 item = node->data;
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) {
431 gchar *filename;
432 person = node->data;
433 filename = addritem_person_get_picture(person);
434 if (filename && is_file_exist(filename)) {
435 if (target == NULL) {
436 target = person;
437 } else {
438 pickPicture = TRUE;
439 target = NULL;
440 g_free(filename);
441 break;
444 if (filename)
445 g_free(filename);
447 if (pickPicture || target) {
448 /* At least one person had a picture */
449 } else if (persons && persons->data) {
450 /* No person had a picture. Use the first person as target */
451 target = persons->data;
452 } else {
453 /* No persons in list. Abort */
454 goto abort;
457 /* Pick which name to keep */
458 for (node = persons; node; node = node->next) {
459 person = node->data;
460 if (nameTarget == NULL) {
461 nameTarget = person;
462 } else if (nameTarget == person) {
463 continue;
464 } else if (g_strcmp0(person->firstName, nameTarget->firstName) ||
465 g_strcmp0(person->lastName, nameTarget->lastName) ||
466 g_strcmp0(person->nickName, nameTarget->nickName) ||
467 g_strcmp0(ADDRITEM_NAME(person), ADDRITEM_NAME(nameTarget))) {
468 pickName = TRUE;
469 break;
472 if (!nameTarget) {
473 /* No persons in list */
474 goto abort;
477 /* Create object */
478 page = g_new0(struct AddrMergePage, 1);
479 page->pickPicture = pickPicture;
480 page->pickName = pickName;
481 page->target = target;
482 page->nameTarget = nameTarget;
483 page->addressSelect = list;
484 page->persons = persons;
485 page->emails = emails;
486 page->clist = clist;
487 page->pobj = pobj;
488 page->abf = abf;
489 page->ds = ds;
491 addrmerge_prompt(page);
492 return;
494 abort:
495 g_list_free( emails );
496 g_list_free( persons );