addrmerge.c (11332B)
1 /* Claws Mail -- a GTK based, lightweight, and fast e-mail client 2 * Copyright (C) 2014-2023 the Claws Mail team and Charles Lehner 3 * 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. 8 * 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. 13 * 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/>. 16 */ 17 18 #include <gdk/gdk.h> 19 #include <gdk/gdkkeysyms.h> 20 #include <glib/gi18n.h> 21 #include <string.h> 22 23 #include "defs.h" 24 25 #include "addrbook.h" 26 #include "addressbook.h" 27 #include "addressitem.h" 28 #include "addrmerge.h" 29 #include "alertpanel.h" 30 #include "gtkutils.h" 31 #include "file-utils.h" 32 #include "utils.h" 33 #include "prefs_common.h" 34 35 enum 36 { 37 COL_DISPLAYNAME, 38 COL_FIRSTNAME, 39 COL_LASTNAME, 40 COL_NICKNAME, 41 N_NAME_COLS 42 }; 43 44 enum 45 { 46 SET_ICON, 47 SET_PERSON, 48 N_SET_COLUMNS 49 }; 50 51 static void addrmerge_done(struct AddrMergePage *page) 52 { 53 g_list_free(page->emails); 54 g_list_free(page->persons); 55 gtk_widget_destroy(GTK_WIDGET(page->dialog)); 56 g_free(page); 57 } 58 59 static void addrmerge_do_merge(struct AddrMergePage *page) 60 { 61 GList *node; 62 ItemEMail *email; 63 ItemPerson *person; 64 ItemPerson *target = page->target; 65 ItemPerson *nameTarget = page->nameTarget; 66 67 gtk_cmclist_freeze(GTK_CMCLIST(page->clist)); 68 69 /* Update target name */ 70 if (nameTarget && nameTarget != target) { 71 target->status = UPDATE_ENTRY; 72 addritem_person_set_first_name( target, nameTarget->firstName ); 73 addritem_person_set_last_name( target, nameTarget->lastName ); 74 addritem_person_set_nick_name( target, nameTarget->nickName ); 75 addritem_person_set_common_name( target, ADDRITEM_NAME(nameTarget )); 76 } 77 78 /* Merge emails into target */ 79 for (node = page->emails; node; node = node->next) { 80 email = node->data; 81 person = ( ItemPerson * ) ADDRITEM_PARENT(email); 82 /* Remove the email from the person */ 83 email = addrbook_person_remove_email( page->abf, person, email ); 84 if( email ) { 85 addrcache_remove_email( page->abf->addressCache, email ); 86 /* Add the email to the target */ 87 addrcache_person_add_email( page->abf->addressCache, target, email ); 88 } 89 person->status = UPDATE_ENTRY; 90 addressbook_folder_refresh_one_person( page->clist, person ); 91 } 92 93 /* Merge persons into target */ 94 for (node = page->persons; node; node = node->next) { 95 GList *nodeE, *nodeA; 96 person = node->data; 97 98 if (person == target) continue; 99 person->status = DELETE_ENTRY; 100 101 /* Move all emails to the target */ 102 for (nodeE = person->listEMail; nodeE; nodeE = nodeE->next) { 103 email = nodeE->data; 104 addritem_person_add_email( target, email ); 105 } 106 g_list_free( person->listEMail ); 107 person->listEMail = NULL; 108 109 /* Move all attributes to the target */ 110 for (nodeA = person->listAttrib; nodeA; nodeA = nodeA->next) { 111 UserAttribute *attrib = nodeA->data; 112 addritem_person_add_attribute( target, attrib ); 113 } 114 g_list_free( person->listAttrib ); 115 person->listAttrib = NULL; 116 117 /* Remove the person */ 118 addrselect_list_remove( page->addressSelect, (AddrItemObject *)person ); 119 addressbook_folder_remove_one_person( page->clist, person ); 120 if (page->pobj->type == ADDR_ITEM_FOLDER) 121 addritem_folder_remove_person(ADAPTER_FOLDER(page->pobj)->itemFolder, person); 122 person = addrbook_remove_person( page->abf, person ); 123 124 if (person) { 125 char filename[PATH_MAX]; 126 if (addritem_person_get_picture(filename, person)) 127 remove(filename); 128 } 129 } 130 131 addressbook_folder_refresh_one_person( page->clist, target ); 132 133 addrbook_set_dirty( page->abf, TRUE ); 134 addressbook_export_to_file(); 135 136 gtk_cmclist_thaw(GTK_CMCLIST(page->clist)); 137 138 addrmerge_done(page); 139 } 140 141 static void addrmerge_dialog_cb(GtkWidget* widget, gint action, gpointer data) { 142 struct AddrMergePage* page = data; 143 144 if (action != GTK_RESPONSE_ACCEPT) 145 return addrmerge_done(page); 146 147 addrmerge_do_merge(page); 148 } 149 150 static void addrmerge_update_dialog_sensitive( struct AddrMergePage *page ) 151 { 152 gboolean canMerge = (page->target && page->nameTarget); 153 gtk_dialog_set_response_sensitive( GTK_DIALOG(page->dialog), 154 GTK_RESPONSE_ACCEPT, canMerge ); 155 } 156 157 static void addrmerge_name_selected( GtkCMCList *clist, gint row, gint column, GdkEvent *event, struct AddrMergePage *page ) 158 { 159 ItemPerson *person = gtk_cmclist_get_row_data( clist, row ); 160 page->nameTarget = person; 161 addrmerge_update_dialog_sensitive(page); 162 } 163 164 static void addrmerge_picture_selected(GtkTreeView *treeview, 165 struct AddrMergePage *page) 166 { 167 GtkTreeModel *model; 168 GtkTreeIter iter; 169 GList *list; 170 ItemPerson *pictureTarget; 171 172 /* Get selected picture target */ 173 model = gtk_icon_view_get_model(GTK_ICON_VIEW(page->iconView)); 174 list = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(page->iconView)); 175 page->target = NULL; 176 if (list != NULL) { 177 if (gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)list->data)) { 178 gtk_tree_model_get(model, &iter, 179 SET_PERSON, &pictureTarget, 180 -1); 181 page->target = pictureTarget; 182 } 183 184 gtk_tree_path_free(list->data); 185 g_list_free(list); 186 } 187 addrmerge_update_dialog_sensitive(page); 188 } 189 190 static void addrmerge_prompt( struct AddrMergePage *page ) 191 { 192 GtkWidget *dialog; 193 GtkWidget *frame; 194 GtkWidget *mvbox, *vbox, *hbox; 195 GtkWidget *label; 196 GtkWidget *iconView = NULL; 197 GtkWidget *namesList = NULL; 198 MainWindow *mainwin = mainwindow_get_mainwindow(); 199 GtkListStore *store = NULL; 200 GList *node; 201 ItemPerson *person; 202 gchar *msg, *label_msg; 203 204 dialog = page->dialog = gtk_dialog_new_with_buttons ( 205 _("Merge addresses"), 206 GTK_WINDOW(mainwin->window), 207 GTK_DIALOG_DESTROY_WITH_PARENT, 208 _("_Cancel"), 209 GTK_RESPONSE_CANCEL, 210 _("_Merge"), 211 GTK_RESPONSE_ACCEPT, 212 NULL); 213 214 g_signal_connect ( dialog, "response", 215 G_CALLBACK(addrmerge_dialog_cb), page); 216 217 mvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4); 218 gtk_container_add(GTK_CONTAINER( 219 gtk_dialog_get_content_area(GTK_DIALOG(dialog))), mvbox); 220 gtk_container_set_border_width(GTK_CONTAINER(mvbox), 8); 221 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); 222 gtk_container_set_border_width(GTK_CONTAINER(hbox), 8); 223 gtk_box_pack_start(GTK_BOX(mvbox), 224 hbox, FALSE, FALSE, 0); 225 226 msg = page->pickPicture || page->pickName ? 227 _("Merging %u contacts." ) : 228 _("Really merge these %u contacts?" ); 229 label_msg = g_strdup_printf(msg, 230 g_list_length(page->addressSelect->listSelect)); 231 label = gtk_label_new( label_msg ); 232 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); 233 gtk_label_set_xalign(GTK_LABEL(label),0); 234 gtk_label_set_yalign(GTK_LABEL(label),0.5); 235 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); 236 g_free(label_msg); 237 238 if (page->pickName) { 239 GtkWidget *scrollwinNames; 240 gchar *name_titles[N_NAME_COLS]; 241 242 name_titles[COL_DISPLAYNAME] = _("Display Name"); 243 name_titles[COL_FIRSTNAME] = _("First Name"); 244 name_titles[COL_LASTNAME] = _("Last Name"); 245 name_titles[COL_NICKNAME] = _("Nickname"); 246 247 store = gtk_list_store_new(N_SET_COLUMNS, 248 GDK_TYPE_PIXBUF, 249 G_TYPE_POINTER, 250 -1); 251 gtk_list_store_clear(store); 252 253 vbox = gtkut_get_options_frame(mvbox, &frame, 254 _("Keep which name?")); 255 gtk_container_set_border_width(GTK_CONTAINER(frame), 4); 256 257 scrollwinNames = gtk_scrolled_window_new(NULL, NULL); 258 gtk_container_set_border_width(GTK_CONTAINER(scrollwinNames), 1); 259 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinNames), 260 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); 261 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinNames), 262 GTK_SHADOW_IN); 263 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scrollwinNames), FALSE, FALSE, 0); 264 265 namesList = gtk_cmclist_new_with_titles(N_NAME_COLS, name_titles); 266 gtk_widget_set_can_focus(GTK_CMCLIST(namesList)->column[0].button, FALSE); 267 gtk_cmclist_set_selection_mode(GTK_CMCLIST(namesList), GTK_SELECTION_BROWSE); 268 gtk_cmclist_set_column_width(GTK_CMCLIST(namesList), COL_DISPLAYNAME, 164); 269 270 gtk_container_add(GTK_CONTAINER(scrollwinNames), namesList); 271 272 /* Add names from persons */ 273 for (node = page->persons; node; node = node->next) { 274 int row; 275 person = node->data; 276 gchar *text[N_NAME_COLS]; 277 text[COL_DISPLAYNAME] = ADDRITEM_NAME(person); 278 text[COL_FIRSTNAME] = person->firstName; 279 text[COL_LASTNAME] = person->lastName; 280 text[COL_NICKNAME] = person->nickName; 281 row = gtk_cmclist_insert( GTK_CMCLIST(namesList), -1, text ); 282 gtk_cmclist_set_row_data( GTK_CMCLIST(namesList), row, person ); 283 } 284 285 g_signal_connect(G_OBJECT(namesList), "select_row", 286 G_CALLBACK(addrmerge_name_selected), page); 287 } 288 289 page->iconView = iconView; 290 page->namesList = namesList; 291 292 addrmerge_update_dialog_sensitive(page); 293 gtk_widget_show_all(dialog); 294 } 295 296 void addrmerge_merge( 297 GtkCMCTree *clist, 298 AddressObject *pobj, 299 AddressDataSource *ds, 300 AddrSelectList *list) 301 { 302 struct AddrMergePage* page; 303 AdapterDSource *ads = NULL; 304 AddressBookFile *abf; 305 gboolean procFlag; 306 GList *node; 307 AddrSelectItem *item; 308 AddrItemObject *aio; 309 ItemPerson *person, *target = NULL, *nameTarget = NULL; 310 GList *persons = NULL, *emails = NULL; 311 gboolean pickName = FALSE; 312 313 if( ds->interface->readOnly ) { 314 alertpanel_warning("This address data is read-only and cannot be deleted."); 315 return; 316 } 317 318 /* Test whether Ok to proceed */ 319 procFlag = FALSE; 320 if( pobj->type == ADDR_DATASOURCE ) { 321 ads = ADAPTER_DSOURCE(pobj); 322 if( ads->subType == ADDR_BOOK ) procFlag = TRUE; 323 } 324 else if( pobj->type == ADDR_ITEM_FOLDER ) { 325 procFlag = TRUE; 326 } 327 else if( pobj->type == ADDR_ITEM_GROUP ) { 328 procFlag = TRUE; 329 } 330 if( ! procFlag ) return; 331 abf = ds->rawDataSource; 332 if( abf == NULL ) return; 333 334 /* Gather selected persons and emails */ 335 for (node = list->listSelect; node; node = node->next) { 336 item = node->data; 337 aio = ( AddrItemObject * ) item->addressItem; 338 if( aio->type == ITEMTYPE_EMAIL ) { 339 emails = g_list_prepend(emails, aio); 340 } else if( aio->type == ITEMTYPE_PERSON ) { 341 persons = g_list_prepend(persons, aio); 342 } 343 } 344 345 if (persons && persons->data) { 346 target = persons->data; 347 } else { 348 /* No persons in list. Abort */ 349 goto abort; 350 } 351 352 /* Pick which name to keep */ 353 for (node = persons; node; node = node->next) { 354 person = node->data; 355 if (nameTarget == NULL) { 356 nameTarget = person; 357 } else if (nameTarget == person) { 358 continue; 359 } else if (g_strcmp0(person->firstName, nameTarget->firstName) || 360 g_strcmp0(person->lastName, nameTarget->lastName) || 361 g_strcmp0(person->nickName, nameTarget->nickName) || 362 g_strcmp0(ADDRITEM_NAME(person), ADDRITEM_NAME(nameTarget))) { 363 pickName = TRUE; 364 break; 365 } 366 } 367 if (!nameTarget) { 368 /* No persons in list */ 369 goto abort; 370 } 371 372 /* Create object */ 373 page = g_new0(struct AddrMergePage, 1); 374 page->pickPicture = FALSE; 375 page->pickName = pickName; 376 page->target = target; 377 page->nameTarget = nameTarget; 378 page->addressSelect = list; 379 page->persons = persons; 380 page->emails = emails; 381 page->clist = clist; 382 page->pobj = pobj; 383 page->abf = abf; 384 page->ds = ds; 385 386 addrmerge_prompt(page); 387 return; 388 389 abort: 390 g_list_free( emails ); 391 g_list_free( persons ); 392 }