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/>.
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"
41 #include "file-utils.h"
43 #include "prefs_common.h"
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
));
69 static void addrmerge_do_merge(struct AddrMergePage
*page
)
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
) {
91 person
= ( ItemPerson
* ) ADDRITEM_PARENT(email
);
92 /* Remove the email from the person */
93 email
= addrbook_person_remove_email( page
->abf
, person
, 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
;
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
) {
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
);
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
);
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();
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
);
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
)
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
));
198 if (gtk_tree_model_get_iter(model
, &iter
, (GtkTreePath
*)list
->data
)) {
199 gtk_tree_model_get(model
, &iter
,
200 SET_PERSON
, &pictureTarget
,
202 page
->target
= pictureTarget
;
205 gtk_tree_path_free(list
->data
);
208 addrmerge_update_dialog_sensitive(page
);
211 static void addrmerge_prompt( struct AddrMergePage
*page
)
215 GtkWidget
*mvbox
, *vbox
, *hbox
;
217 GtkWidget
*iconView
= NULL
;
218 GtkWidget
*namesList
= NULL
;
219 MainWindow
*mainwin
= mainwindow_get_mainwindow();
220 GtkListStore
*store
= NULL
;
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
,
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);
261 if (page
->pickPicture
) {
262 GtkWidget
*scrollwinPictures
;
264 store
= gtk_list_store_new(N_SET_COLUMNS
,
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
),
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
) {
294 filename
= addritem_person_get_picture(person
);
295 if (filename
&& is_file_exist(filename
)) {
299 pixbuf
= gdk_pixbuf_new_from_file(filename
, &error
);
301 debug_print("Failed to read image: \n%s",
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
,
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
,
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
),
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
) {
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(
382 AddressDataSource
*ds
,
383 AddrSelectList
*list
)
385 struct AddrMergePage
* page
;
386 AdapterDSource
*ads
= NULL
;
387 AddressBookFile
*abf
;
390 AddrSelectItem
*item
;
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."));
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
) {
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
;
453 /* No persons in list. Abort */
457 /* Pick which name to keep */
458 for (node
= persons
; node
; node
= node
->next
) {
460 if (nameTarget
== NULL
) {
462 } else if (nameTarget
== person
) {
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
))) {
473 /* No persons in list */
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
;
491 addrmerge_prompt(page
);
495 g_list_free( emails
);
496 g_list_free( persons
);